Expand description
Durable ownership of the shared Cargo build cache (CARGO_TARGET_DIR).
One cache, many borrowers: an implement wave’s candidates, a review
round’s E2E, the final gate, a human’s magi review invoked by hand — all
of them may point CARGO_TARGET_DIR at the same directory, sometimes from
different worktrees, sometimes from a different magi run entirely. Two
failures came out of sharing it with no ownership record at all:
- A read-only reviewer seat that inherited
CARGO_TARGET_DIRtried to write into it, was refused by its own sandbox, and reported the refusal as a defect in the code under review rather than a property of its own seat. Seegraph::wave, which now only hands the variable to a seat thatcrate::agent::Invocation::allow_writeactually permits to use. - Two source trees building the same package name/version into one
CARGO_TARGET_DIRin sequence can leave a stale artifact from the older worktree looking fresh to Cargo, and a latercargo testruns binaries compiled from source nobody is looking at.ensure_freshselectivelycargo clean -ps the workspace’s own packages — never the downloaded dependency graph — the moment the recorded source identity for a cache directory changes.
Both are consequences of one missing fact: who is using this cache right
now, and against which source. This module is that fact, made durable
(a JSON file per cache directory, surviving a process restart) and cheap
to ask (classify, in_use, inventory).
§Reclaiming a dead owner
Liveness is crate::proc::pid_alive — the same conservative check
crate::daemon::sweep_stale_claims uses for the queue’s own claim
locks: every uncertain outcome reads as alive, and a lease whose file
cannot even be parsed is never reclaimed automatically (Status::Unknown).
This is deliberately the same policy as the queue’s .lock files, not a
new one — a bare lock file was never trusted alone there either; it is
always paired with a liveness check.
§What this module does not do
It does not reap a build’s orphaned grandchildren once magi kills the
seat that started them at a timeout — that is process-tree ownership, a
different problem with its own owner elsewhere. A lease held by a process
whose pid has exited is Stale and reclaimable as a lease, whether or
not a grandchild is still writing files under the cache; this module only
ever answers “who owns the cache directory”, not “is every process that
might still be touching it definitely gone”.
Structs§
- Entry
- One cache directory’s state, for
a0fc’s capacity/cleanup inventory (andmagi cache show, eventually). Every lease file on disk is reported — including ones this process itself does not want to touch — because the question this answers is “what is registered right now”, not “what can I safely act on”. - Guard
- A held lease. Releases on drop, so a panicking or early-returning caller
never leaves the cache permanently marked busy — the same guarantee
crate::queue::Claimgives the task queue’s own lock file. - Identity
- The source a build against a cache directory was last known to come from.
Compared by
needs_refreshon every acquire, so a cache directory that only ever sees one worktree/head pair never pays a clean it does not need. - Owner
- Who is holding, or wants, a cache directory.
Enums§
- Acquire
Outcome - What
try_acquirereturned. - Busy
- Why
try_acquiredid not hand back aGuard. - Entry
Status Entry::status’s possible values.- Status
- Where a cache directory’s classification lands.
Functions§
- ensure_
fresh - Guarantee that a build against
cache_dirfrom(worktree, head)never silently reuses another source’s compiled output: clean the workspace’s own packages out of the cache when the recorded identity disagrees, then record the new one. A no-op — no subprocess spawned — when the identity already matches, which is the common case once a cache directory settles on one worktree for a while. - in_use
- Is
cache_dirin use right now — held by a live owner, or in a state nobody can vouch for? The conservative half of the janitor’s prune guard:Status::Unknownrefuses exactly likeStatus::Active, because a lease this process cannot read is not evidence the directory is free. - invalidate_
identity - Forget the recorded source identity for
cache_dir, so the nextensure_freshcall cannot skip its clean on the strength of a stale match. - inventory
- Every cache directory magi knows about under
home, whether or not anything holds it right now. A lease file reports the current borrower; once released it is gone (by design — seeGuard::release), so a directory nobody is borrowing is reported from the catalog instead, carrying its last known owner rather than nothing at all. There is nothing here for a directory this process has never leased:inventoryanswers “what has magi registered”, not “what looks like a build cache”. - maintenance_
prune - Prune
cache_dirtolimit, but only while holding the lease — a prune that raced a live build would delete files a compile in flight still needs, on top of confusing whatever built them about why its own output vanished.Ok(None)when the cache is in use right now; that is not an error, it is the janitor’s next pass catching it once the borrower is done. Never touches a lease file itself — they live outsidecache_dir(see [leases_dir]), socrate::disk::prune_dir’s own sweep can never reach one. - needs_
refresh - Has the source building against
cache_dirchanged since the last record? True (needs a refresh) whenever nothing was ever recorded — the conservative default for a cache directory this process has not tracked before. - parse_
workspace_ package_ names - Parse
cargo metadata --no-deps’s JSON for the names of packages defined in the workspace itself — never a dependency, which is exactly the distinction that keeps a freshness fix from also discarding a downloaded crate’s compiled artifacts on every worktree switch. Pure, so the parse is tested against fixture text without acargoon the test machine. - record_
identity - Persist
identityas the last known source forcache_dir. - try_
acquire - Take the lease for
cache_dirif nobody live holds it, reclaiming a stale one first. One attempt — a caller that wants to wait useswait_for, which is this in a loop bounded by a budget. - wait_
for - Acquire the lease for
cache_dir, waiting out contention rather than failing on the first busy owner — but never pastbudget, and the wait comes out of that same budget rather than a second, unbounded one. A caller already has a node timeout; this is that timeout, not a new clock next to it, which is what keeps a wait from becoming the “無期限待機” AGENTS.md’s own build-cache section warns is never acceptable. The error on timeout names who is still holding it, for the event this gets logged into.