Per-user build cache discipline — preventing cross-user Cargo races
content(quality-pass): institution POV elevation — 97 articles updated, 32 new from drafts-outbound
@@ -7,21 +7,21 @@ category: architecture type: topic status: active bcsc_class: public-disclosure-safe last_edited: 2026-05-18 last_edited: 2026-05-25 editor: pointsav-engineering cites: [] paired_with: cargo-target-per-user-discipline.es.md --- Cargo's incremental build cache lives under `target/`. By default that is a per-crate directory inside each crate. The workspace overrides this via `CARGO_TARGET_DIR=/srv/foundry/cargo-target/` so the build cache is shared across all crates in the monorepo — saving disk space and rebuild time. Cargo's incremental build cache lives under `target/`. By default that is a per-crate directory inside each crate. The workspace overrides this via the `CARGO_TARGET_DIR` environment variable pointing at a shared path, so the build cache is shared across all crates in the monorepo — saving disk space and rebuild time. This works for a single developer. With two developers in the workspace group it becomes a race. Cargo's `target/.cargo-lock` serialises full-tree builds, so two `cargo build` invocations on different crates block one another. Worse, mixed file ownership — build artefacts written by one user interleaved with another user's — causes sporadic `Permission denied` errors during incremental rebuild, even when the crates appear to be unrelated. The fix: `/etc/profile.d/foundry-cargo.sh` exports `CARGO_TARGET_DIR=/srv/foundry/cargo-target/$USER/`. Each user gets a private subdirectory. The build cache stays shared within a user — the original optimisation is preserved — but no longer crosses users. The environment variable takes precedence over `~/.cargo/config.toml`'s `target-dir` setting, so the change takes effect on any new shell without per-user dotfile edits. The fix: a system profile script exports `CARGO_TARGET_DIR` pointing at a per-user subdirectory (`<shared-root>/$USER/`). Each user gets a private subdirectory. The build cache stays shared within a user — the original optimisation is preserved — but no longer crosses users. The environment variable takes precedence over `~/.cargo/config.toml`'s `target-dir` setting, so the change takes effect on any new shell without per-user dotfile edits. The migration involved moving the existing 3.3 GiB cache from `/srv/foundry/cargo-target/{release,debug,cxxbridge,tmp}` into one user's subdirectory. That user's incremental state is preserved; the other developer starts with an empty cache that fills on first build. The migration involved moving the existing 3.3 GiB cache into one user's subdirectory. That user's incremental state is preserved; the other developer starts with an empty cache that fills on first build. Service accounts in the workspace group (local-doorman, local-design, and similar) do not run Cargo and do not receive a per-user cache directory. The profile.d script only exports when `/srv/foundry/cargo-target/` exists, so non-workspace contexts are unaffected. Service accounts in the workspace group (local-doorman, local-design, and similar) do not run Cargo and do not receive a per-user cache directory. The profile script only exports when the shared cache root exists, so non-workspace contexts are unaffected. The lesson generalises: any group-writable shared workspace path needs explicit design around which operations are safe to share (reads, content appends) and which require per-user partition (build state, lock files, temp). Cargo is the prominent example; the same logic applies to anything with mutable state under a shared root.