fix(ci): serialize concurrent cargo cache mounts with sharing=locked (FJCLI-47) #60

Merged
David merged 1 commit from fix/FJCLI-47-cargo-cache-sharing-locked into main 2026-07-02 16:24:49 +02:00
Owner

Fixes the build-binary.yml failure on main (run #287, FJCLI-47): a BuildKit cargo-cache race that surfaced right after the FJCLI-45 MCP merge.

Root cause

build-binary.yml (Linux) and build-binary-windows.yml (Windows) trigger on the same crates/** / Cargo.* / oci-build/** paths, so the post-merge push to main ran both concurrently on one runner. Both Dockerfiles mount the cargo registry/git BuildKit caches at the same target path (/usr/local/cargo/registry), which is the default cache id, so they share one cache. BuildKit defaults to sharing=shared, and cargo's .package-cache extraction lock lives in the ephemeral $CARGO_HOME (not on the cached mount), so the two builds cannot see each other's lock. They raced unpacking the same crate and collided on .cargo-ok: failed to open .cargo-ok / File exists (os error 17).

The MCP merge added the fj-mcp dependency tree (schemars, serde_derive_internals, icu_*), which enlarged the concurrent fresh-download window enough to trip a race that was latent before.

Fix

Add sharing=locked to all eight cargo cache mounts across oci-build/Dockerfile and oci-build/Dockerfile.windows so concurrent builds serialize through the shared cache instead of racing. This is exactly the fix already present in the sibling pandoras-box/forgejo-mcp Dockerfiles, which run three concurrent builds on one runner. The two cache-using RUNs within a single Dockerfile are already sequential, so the lock adds no cost there.

This supersedes the auto-triage's "clear the runner cache" suggestion with a durable source-level fix that prevents recurrence.

Completeness sweep

Invariant: every cargo registry/git cache mount a concurrent build can touch must declare sharing=locked. Grep of oci-build/ found 8 mounts (registry + git, in the prime and real build RUNs of each Dockerfile); all 8 were shared and are now locked. No exclusions.

Testing

  • just check-docker (builder-stage compile of oci-build/Dockerfile with the locked mounts): green, image exported.
  • just pre-commit (fmt + clippy + build + full test suite): green (no Rust changed).

One-time infra note

The runner's cargo registry cache volume may still hold the half-unpacked serde_derive_internals entry from the failed run. A one-time prune of that BuildKit cache clears it; after that, sharing=locked prevents the race from recurring.

Fixes the `build-binary.yml` failure on `main` (run #287, FJCLI-47): a BuildKit cargo-cache race that surfaced right after the FJCLI-45 MCP merge. ## Root cause `build-binary.yml` (Linux) and `build-binary-windows.yml` (Windows) trigger on the same `crates/**` / `Cargo.*` / `oci-build/**` paths, so the post-merge push to `main` ran both concurrently on one runner. Both Dockerfiles mount the cargo registry/git BuildKit caches at the same target path (`/usr/local/cargo/registry`), which is the default cache id, so they share one cache. BuildKit defaults to `sharing=shared`, and cargo's `.package-cache` extraction lock lives in the ephemeral `$CARGO_HOME` (not on the cached mount), so the two builds cannot see each other's lock. They raced unpacking the same crate and collided on `.cargo-ok`: `failed to open .cargo-ok / File exists (os error 17)`. The MCP merge added the `fj-mcp` dependency tree (`schemars`, `serde_derive_internals`, `icu_*`), which enlarged the concurrent fresh-download window enough to trip a race that was latent before. ## Fix Add `sharing=locked` to all eight cargo cache mounts across `oci-build/Dockerfile` and `oci-build/Dockerfile.windows` so concurrent builds serialize through the shared cache instead of racing. This is exactly the fix already present in the sibling `pandoras-box/forgejo-mcp` Dockerfiles, which run three concurrent builds on one runner. The two cache-using RUNs within a single Dockerfile are already sequential, so the lock adds no cost there. This supersedes the auto-triage's "clear the runner cache" suggestion with a durable source-level fix that prevents recurrence. ## Completeness sweep Invariant: every cargo registry/git cache mount a concurrent build can touch must declare `sharing=locked`. Grep of `oci-build/` found 8 mounts (registry + git, in the prime and real build RUNs of each Dockerfile); all 8 were `shared` and are now `locked`. No exclusions. ## Testing - `just check-docker` (builder-stage compile of `oci-build/Dockerfile` with the locked mounts): green, image exported. - `just pre-commit` (fmt + clippy + build + full test suite): green (no Rust changed). ## One-time infra note The runner's cargo registry cache volume may still hold the half-unpacked `serde_derive_internals` entry from the failed run. A one-time prune of that BuildKit cache clears it; after that, `sharing=locked` prevents the race from recurring.
fix(ci): serialize concurrent cargo cache mounts with sharing=locked
All checks were successful
Check / fmt + clippy + build + tests (pull_request) Successful in 45s
Create release / Create release from merged PR (pull_request) Has been skipped
7290487456
The Linux (build-binary.yml) and Windows (build-binary-windows.yml) binary builds trigger on the same paths and run concurrently on one runner, sharing the BuildKit cargo registry/git cache mounts (BuildKit defaults to sharing=shared). Cargo's .package-cache extraction lock lives in the ephemeral $CARGO_HOME, not on the cached mount, so the two builds cannot see each other's lock and race unpacking the same crate, colliding on .cargo-ok with "File exists (os error 17)". The FJCLI-45 merge added the fj-mcp dependency tree (schemars, serde_derive_internals, icu_*), enlarging the concurrent fresh-download window enough to hit the race on the post-merge push to main (run #287).

Add sharing=locked to all eight cargo cache mounts across oci-build/Dockerfile and oci-build/Dockerfile.windows so concurrent builds serialize through the shared cache instead of racing. This mirrors the fix already present in the sibling pandoras-box/forgejo-mcp Dockerfiles. The two cache-using RUNs within a single Dockerfile are already sequential, so the lock adds no cost there.

Note: the runner's cargo registry cache volume may still hold the half-unpacked serde_derive_internals entry from the failed run; a one-time prune of that BuildKit cache clears it, after which sharing=locked prevents recurrence.

#FJCLI-47

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: David Randall <David@NiceGuyIT.biz>
David scheduled this pull request to auto merge when all checks succeed 2026-07-02 16:24:10 +02:00
David merged commit c486f25999 into main 2026-07-02 16:24:49 +02:00
David deleted branch fix/FJCLI-47-cargo-cache-sharing-locked 2026-07-02 16:24:50 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
pandoras-box/forgejo-cli!60
No description provided.