Skip to content

Worktrees share what cargo compiles on the way, and keep their own finished artifacts

Worktrees share what cargo compiles on the way, and keep their own finished artifacts

majordomus://adr/adr-0139proposed operationsworktreesdisk

it references

Nothing. This object declares no reference.

referenced by

139. Worktrees share what cargo compiles on the way, and keep their own finished artifacts

Context

ADR 0055 made disk a measured quantity and gave each worktree its own target/, reclaimed by predicate once the branch has landed. That bounded the accumulation but not the duplication: every worktree compiles the same dependencies into its own directory, and one cold build is 2.45GiB (measured 2026-09-12). On 2026-10-11 the machine sat 2GB under the 5120MB floor that refuses a build, and the build that was refused was one more copy of dependencies another worktree had already compiled.

Two ways of sharing were on the table.

A shared CARGO_TARGET_DIR is supported by the repository today and is what several scripts already tolerate. It also shares the finished executable: whichever worktree built last has overwritten debug/majordomus, so a worktree's own mj_rust_stale check, its MAJORDOMUS_BIN borrowers and a server holding a lease all find another branch's binary under a path that says nothing about it. That is the failure project.reclaim-only-what-you-own was written about, produced by construction.

sccache was measured as the alternative on this machine and gave no cache hit between two different target directories (0 hits, 28 misses over two builds of serde and regex); a rebuild into the same directory hit. It stays optional and is not part of this decision.

Cargo's build.build-dir splits the two things a target directory holds: intermediates (dependency units, fingerprints, incremental state) go to build-dir, and finished artifacts go to target-dir. It is accepted without -Z by the toolchain this repository pins (1.98.1), which is what was checked; the release that stabilised it was not.

Decision

.cargo/config.toml sets build.build-dir to one directory under cargo's home, {cargo-cache-home}/build-shared/majordomus, and does not set target-dir. Every worktree therefore compiles a dependency once and keeps its own target/ holding only its own finished artifacts.

The location is under cargo's home because a committed file cannot hold a relative path that is right at every worktree depth (<repo>-wt/feat/x and <repo>-wt/x differ), and an absolute one would name a person's machine.

The directory belongs to no worktree, so the per-worktree predicates of ADR 0055 cannot classify it. scripts/reap-orphans reads it from the checkout's own declaration, measures it with the worktrees, bounds it at 6144MB (MAJORDOMUS_SHARED_BUILD_CAP_MB, two cold builds) and, under --reclaim, removes it as a whole when it is over its bound and no cargo or rustc process is running. It is regenerable, so the cost of a wrong removal is one dependency rebuild and cannot be a byte of authored source. A checkout that declares no build-dir has nothing reclaimed, so a fixture or an adopter is never acted on by a default.

Alternatives rejected

  • A shared CARGO_TARGET_DIR. Shares the executable, as above.
  • sccache as the mechanism. No cross-directory hit was measured; it adds a daemon and a second cache to bound.
  • A target-dir symlink per worktree. The ownership test of ADR 0055 refuses to treat a symlinked directory as the worktree's own, correctly, so the reaper would never touch it.
  • A policy key that generates .cargo/config.toml. Better in the end — one declaration under .ai/ and a generated projection — and a Rust change to the generator; it is the follow-up, and the config file carries the one line meanwhile.

Consequences

  • A second worktree's cold build reuses the first one's dependencies: measured 11s to 1s on a two-dependency fixture (test/cases/1022). The repository's own saving has not been measured and is the number to read after a week of use.
  • Dependencies are shared. Whether the workspace crate is also compiled once per worktree was not measured; it is expected to be, since a path source's identity includes its path.
  • Builds that share the directory serialise on cargo's file lock: two worktrees building at once wait for each other. That is the cost, and derive already runs one at a time. Amended 2026-10-13. The sentence counted one builder. The commit hook's launcher, the lints, the tests and the documentation build start cargo as well, in every worktree, and on that day one of them stood behind the lock for fourteen minutes in silence, because --quiet removes the line in which cargo says it is blocked. The cost is now bounded rather than accepted: a build waits MAJORDOMUS_BUILD_LOCK_WAIT seconds at most, says what it waited behind, and then builds in the tree's own directory (mj_cargo_bounded, project.a-build-never-waits-in-silence-on-anothers-lock). Sharing stays the default and is what a build gets when nobody is in the way; a private directory is the price of not waiting, paid only by the build that would have waited.
  • Intermediates are in one place, so cargo clean in a worktree cleans its artifacts and not the shared directory, which is what the ownership rule wants.
  • The shared directory is outside every checkout, so removing a worktree never frees it; only the reaper's bound does. That is the new accumulation and its measure, threshold and reducer are named in project.worktrees-share-build-intermediates.
  • Proven by test/cases/1022_worktrees_share_build_intermediates.sh.

Where this comes from

canonical file
.ai/repo/adrs/0139-worktrees-share-what-cargo-compiles-on-the-way-and-keep-thei.md
identity
adr-0139
also served as
MCP resource majordomus://adr/adr-0139 · majordomus entity show adr/adr-0139 · GET /api/v1/entity?uri=majordomus://adr/adr-0139
in the Cockpit
/cockpit/objects/adr/adr-0139

This page is generated from the file above by scripts/generate-site-data; editing the page changes nothing, editing the file changes every surface. The relations above are edges of docs/generated/graph.json, which the executable writes from the same index.

Searches the titles and descriptions of every page this site publishes.