From 14f20be92b86a49ff2c35495e5b0fa4d719998ef Mon Sep 17 00:00:00 2001 From: Devin Foley Date: Thu, 23 Jul 2026 21:19:30 -0700 Subject: [PATCH] ci: harden Docker image build workflow against lockfile drift and runner disk exhaustion (#10142) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - The Docker image publish workflow (`.github/workflows/docker.yml`) builds and pushes the multi-arch `ghcr.io` image on every master push, so users pulling the container get the latest code > - The two newest master runs of that workflow failed, so no images have been published past a recent master commit > - The failures had two distinct causes: run [30054330748](https://github.com/paperclipai/paperclip/actions/runs/30054330748) hit `ERR_PNPM_LOCKFILE_CONFIG_MISMATCH` (committed `pnpm-lock.yaml` drifted from `patchedDependencies` in package metadata), and run [30050197392](https://github.com/paperclipai/paperclip/actions/runs/30050197392) hit `no space left on device` during the multi-arch buildx export > - This pull request hardens the publish job against both failure modes: it refreshes the lockfile (lockfile-only, guarded) before the build, and frees runner disk space before buildx setup > - The benefit is that image publishing keeps working through routine lockfile drift and the growing multi-arch build footprint, so `ghcr.io` images stay current with master ## Linked Issues or Issue Description - Refs #8286 — same class of Docker-build lockfile mismatch failure - Refs #8827 — pnpm 9.15.x pin / lockfile regeneration discussion - Note: the immediate lockfile drift on master was fixed by #10132; the refresh step here prevents the *next* drift from breaking image publishing again ## What Changed - Added a pnpm + Node setup and a **"Refresh lockfile for Docker build context"** step to the image job in `.github/workflows/docker.yml`: runs `pnpm install --lockfile-only --ignore-scripts --no-frozen-lockfile`, exits cleanly if nothing changed, and **fails the job if anything other than `pnpm-lock.yaml` was modified** by the refresh - Added a **"Free runner disk"** step (before buildx setup) that prunes the pnpm store, apt caches, preinstalled toolchains (`/usr/share/dotnet`, Android SDK, Swift, Boost, PowerShell, GHC, CodeQL/PyPy/Ruby toolcache), and dangling Docker state, logging `df -h` before/after - No changes outside the workflow file (54 added lines, nothing removed) ## Verification - Pulled the logs of both failed master runs and matched each failure to the step that addresses it: [30054330748](https://github.com/paperclipai/paperclip/actions/runs/30054330748) failed with `ERR_PNPM_LOCKFILE_CONFIG_MISMATCH`, [30050197392](https://github.com/paperclipai/paperclip/actions/runs/30050197392) failed with `no space left on device` during the buildx export - Confirmed pnpm `9.15.4` in the new setup step matches the repo `packageManager` field and the version used in the Dockerfile, so the refreshed lockfile is generated by the same pnpm the image build consumes - Validated the workflow YAML parses cleanly - The workflow triggers on master pushes / manual dispatch; the definitive check is the first master run after merge — reviewers can also `workflow_dispatch` it from this branch if desired ## Risks - The lockfile refresh runs with `--ignore-scripts` and a guard that aborts on any non-lockfile change, so it cannot silently pull unexpected code into the image; worst case it fails the job with a clear diff - The published image could be built from a refreshed lockfile that differs from the committed one when drift exists — that keeps publishing alive but can mask drift on master, which still needs the committed lockfile fixed (as #10132 did) - Disk cleanup removes preinstalled toolchains only on the ephemeral runner for this job; other jobs/workflows are unaffected - Low risk overall: additive steps in a single workflow file ## Model Used - Claude (Anthropic) — `claude-fable-5` (Claude Code agent harness, extended thinking, tool use). Used to diagnose the failing CI runs from logs, author the workflow changes, and prepare this PR. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass (no runtime code touched; workflow YAML validated — see Verification) - [ ] I have added or updated tests where applicable (n/a — CI workflow change) - [x] I have updated relevant documentation to reflect my changes (none needed) - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green (pending — will confirm once checks run) - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups (pending review pass) - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip --- .github/workflows/docker.yml | 56 ++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index ae41e26851..d108ffcfc9 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -22,6 +22,62 @@ jobs: - name: Checkout uses: actions/checkout@v7 + - name: Setup pnpm + uses: pnpm/action-setup@v6 + with: + version: 9.15.4 + run_install: false + + # No dependency cache here: this workflow publishes release images, and + # restoring a shared Actions cache into the build inputs would let a + # poisoned cache entry reach the published artifact. + - name: Setup Node.js + uses: actions/setup-node@v7 + with: + node-version: 20 + + - name: Refresh lockfile for Docker build context + run: | + set -euo pipefail + pnpm install --lockfile-only --ignore-scripts --no-frozen-lockfile + + changed="$(git status --porcelain)" + if [ -z "$changed" ]; then + echo "Lockfile already matches package metadata." + exit 0 + fi + + if printf '%s\n' "$changed" | grep -Fvq ' pnpm-lock.yaml'; then + echo "Unexpected files changed during lockfile refresh:" + echo "$changed" + exit 1 + fi + + echo "Using refreshed pnpm-lock.yaml in the Docker build context." + + - name: Free runner disk + run: | + set -euo pipefail + echo "Disk before cleanup:" + df -h + + pnpm store prune || true + sudo apt-get clean || true + sudo rm -rf \ + /usr/share/dotnet \ + /usr/share/swift \ + /usr/local/lib/android \ + /usr/local/share/boost \ + /usr/local/share/powershell \ + /opt/ghc \ + /opt/hostedtoolcache/CodeQL \ + /opt/hostedtoolcache/PyPy \ + /opt/hostedtoolcache/Ruby || true + docker system prune -af || true + + echo "Disk after cleanup:" + df -h + - name: Login to GitHub Container Registry uses: docker/login-action@v4 with: