From c44b5342d5ebf1e37502891c45601e3de0649cc6 Mon Sep 17 00:00:00 2001 From: AI Agent Bot Date: Sat, 21 Feb 2026 16:02:49 -0600 Subject: [PATCH 1/3] Fix Docker build cache poisoning and add Docker build to PR validation The v0.1.0 release pipeline failed because the Dockerfile's dependency pre-caching strategy uses stub source files with --mount=type=cache, but cargo fingerprints in the cache are not invalidated when real source files replace the stubs. This causes cargo to skip recompiling workspace crates, linking the server against empty stubs (41 compile errors). Fix by purging stale cargo fingerprints for workspace crates between the stub dependency build and the real source build. Also add a Docker Build Check job to pr-validation.yml so Docker image builds are validated before merging, preventing this class of failure from reaching release. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/pr-validation.yml | 44 ++++++++++++++++++++++++++++- docker/server.Dockerfile | 7 +++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pr-validation.yml b/.github/workflows/pr-validation.yml index 3218b79..7d2389f 100644 --- a/.github/workflows/pr-validation.yml +++ b/.github/workflows/pr-validation.yml @@ -106,6 +106,47 @@ jobs: fi done + # ── Docker Build Check ─────────────────────────────────────── + docker-build: + name: Docker Build Check + needs: ci + runs-on: self-hosted + timeout-minutes: 90 + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Build Docker image (no push) + run: docker build -f docker/server.Dockerfile -t breakpoint:pr-${{ github.event.pull_request.number || 'manual' }} . + + - name: Verify Docker image starts + run: | + docker run --rm -d --name bp-docker-check \ + -p 19080:8080 \ + breakpoint:pr-${{ github.event.pull_request.number || 'manual' }} + for i in $(seq 1 30); do + if curl -sf http://127.0.0.1:19080/api/v1/status > /dev/null 2>&1; then + echo "Docker image healthcheck passed" + break + fi + if [ "$i" -eq 30 ]; then + echo "::error::Docker image failed healthcheck after 15s" + docker logs bp-docker-check + docker stop bp-docker-check 2>/dev/null || true + exit 1 + fi + sleep 0.5 + done + docker stop bp-docker-check + + - name: Clean up Docker image + if: always() + run: | + docker stop bp-docker-check 2>/dev/null || true + docker rmi breakpoint:pr-${{ github.event.pull_request.number || 'manual' }} 2>/dev/null || true + # ── Browser Tests (Playwright, containerized) ───────────────── browser-tests: name: Browser Tests @@ -574,7 +615,7 @@ jobs: # ── PR Status Summary ────────────────────────────────────────── pr-status: name: PR Status Summary - needs: [ci, config, browser-tests, gemini-review, codex-review, agent-review-response, agent-failure-handler] + needs: [ci, config, docker-build, browser-tests, gemini-review, codex-review, agent-review-response, agent-failure-handler] if: always() runs-on: self-hosted steps: @@ -585,6 +626,7 @@ jobs: echo "| Check | Status |" >> $GITHUB_STEP_SUMMARY echo "|-------|--------|" >> $GITHUB_STEP_SUMMARY echo "| CI | ${{ needs.ci.result }} |" >> $GITHUB_STEP_SUMMARY + echo "| Docker Build | ${{ needs.docker-build.result }} |" >> $GITHUB_STEP_SUMMARY echo "| Browser Tests | ${{ needs.browser-tests.result }} |" >> $GITHUB_STEP_SUMMARY echo "| Gemini Review | ${{ needs.gemini-review.result }} |" >> $GITHUB_STEP_SUMMARY echo "| Codex Review | ${{ needs.codex-review.result }} |" >> $GITHUB_STEP_SUMMARY diff --git a/docker/server.Dockerfile b/docker/server.Dockerfile index b1341d4..b57255f 100644 --- a/docker/server.Dockerfile +++ b/docker/server.Dockerfile @@ -52,6 +52,13 @@ RUN --mount=type=cache,target=/usr/local/cargo/registry \ # Copy actual source code COPY crates/ crates/ +# Purge stale cargo fingerprints for workspace crates so cargo detects +# that the real sources replaced the stubs. The cache mount preserves +# fingerprints from the stub build above, which would cause cargo to +# skip recompiling workspace crates against the real source code. +RUN --mount=type=cache,target=/build/target \ + find /build/target -name "breakpoint*" -path "*/fingerprint/*" -exec rm -rf {} + 2>/dev/null; true + # Build server binary RUN --mount=type=cache,target=/usr/local/cargo/registry \ --mount=type=cache,target=/build/target \ From d795ee47befaaf5e8f1e92b7692b773c331361fa Mon Sep 17 00:00:00 2001 From: AI Agent Bot Date: Sat, 21 Feb 2026 16:33:59 -0600 Subject: [PATCH 2/3] Fix fingerprint path pattern: cargo uses .fingerprint (dotted) The find command used */fingerprint/* but cargo stores fingerprints in a .fingerprint directory (with leading dot). This caused the purge to match nothing, leaving stale stub fingerprints intact. Co-Authored-By: Claude Opus 4.6 --- docker/server.Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/server.Dockerfile b/docker/server.Dockerfile index b57255f..2980578 100644 --- a/docker/server.Dockerfile +++ b/docker/server.Dockerfile @@ -57,7 +57,7 @@ COPY crates/ crates/ # fingerprints from the stub build above, which would cause cargo to # skip recompiling workspace crates against the real source code. RUN --mount=type=cache,target=/build/target \ - find /build/target -name "breakpoint*" -path "*/fingerprint/*" -exec rm -rf {} + 2>/dev/null; true + find /build/target -name "breakpoint*" -path "*/.fingerprint/*" -exec rm -rf {} + 2>/dev/null; true # Build server binary RUN --mount=type=cache,target=/usr/local/cargo/registry \ From a3583801908e5dbed37d077899401f9ff0c61503 Mon Sep 17 00:00:00 2001 From: AI Agent Bot Date: Sat, 21 Feb 2026 17:08:13 -0600 Subject: [PATCH 3/3] Copy web/theme.json into builder stage for WASM compile The breakpoint-client crate uses include_str!("../../../web/theme.json") at compile time, but the Dockerfile only copied crates/ into the builder stage. The web/ directory was only copied into the runtime stage. Verified: Docker image builds successfully end-to-end locally. Co-Authored-By: Claude Opus 4.6 --- docker/server.Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docker/server.Dockerfile b/docker/server.Dockerfile index 2980578..68fc771 100644 --- a/docker/server.Dockerfile +++ b/docker/server.Dockerfile @@ -49,8 +49,9 @@ RUN --mount=type=cache,target=/usr/local/cargo/registry \ --mount=type=cache,target=/build/target \ cargo build --release -p breakpoint-server --features github-poller; exit 0 -# Copy actual source code +# Copy actual source code and compile-time assets COPY crates/ crates/ +COPY web/theme.json web/theme.json # Purge stale cargo fingerprints for workspace crates so cargo detects # that the real sources replaced the stubs. The cache mount preserves