Skip to main content

Agent Worktree Isolation — Runbook

Status: active — 2026-04-22. Owner: devops-engineer. Related: core#852, client#107, client#110, client#111, core#827, core#3240.

Why this exists​

Multiple agents (backend-sme, frontend-sme, qa-engineer, principal-architect, etc.) are frequently dispatched in parallel on the same repo. When they all operate on the shared main checkout (/opt/upsquad/<repo>/), their git checkout, git stash, and git clean operations silently clobber each other's uncommitted work.

Direct incidents:

IncidentCostNote
client#107 (Clerk webhook)multiple rebuilds"checkouts got clobbered twice mid-build"
client#110 (dev-bypass removal)~90 minutesuntracked files dropped + tracked edits reverted when parallel branches flipped
client#111 (stub regen)setup tax per agent"concurrent bot sessions so explicit branch checkout + stash-by-id is required"
core#827 (conflict resolution)forced workaroundagent created an ad-hoc worktree at /tmp/upsquad-761-merge

Convention​

Every agent task works in a dedicated git worktree at /opt/upsquad-worktrees/<repo>/<agent>-<issue>/, branched from a freshly fetched origin/main.

SlotValue
Root/opt/upsquad-worktrees/<repo>/ — group-owned by upsquad-devs, mode 2775 (setgid)
Worktree dir<agent>-<issue>
Branch<prefix>/<issue>-<agent> (e.g. fix/844-devops-engineer)
Prefixone of fix, feat, chore, docs, refactor, test
Baseorigin/main at dispatch time

One-time bootstrap (per dev box / CI runner)​

sudo mkdir -p /opt/upsquad-worktrees/upsquad-core \
/opt/upsquad-worktrees/upsquad-client \
/opt/upsquad-worktrees/upsquad-admin \
/opt/upsquad-worktrees/upsquad-web
sudo chown -R "$USER":upsquad-devs /opt/upsquad-worktrees
sudo chmod 2775 /opt/upsquad-worktrees
sudo chmod 2775 /opt/upsquad-worktrees/*

The setgid bit on each directory makes new files/subdirs inherit the upsquad-devs group so other agents (under any upsquad-devs user) can read/write them.

Per-dispatch​

# Agent prompts receive the issue number. The helper script returns the
# worktree path on stdout; all informational logs go to stderr.
WT=$(AGENT_NAME=backend-sme bash /opt/upsquad/upsquad-core/scripts/agent-worktree.sh 1234)
cd "$WT"
# work, commit, push ...

Script behaviour:

  • Fetches origin/main fresh, branches from it.
  • Pins the worktree's commit identity to the agent (see below).
  • If the worktree already exists (agent retry), it re-asserts the identity, prints the path and exits 0 — agents can idempotently call it.
  • If the branch already exists locally (previous attempt), it reuses the branch and the new worktree checks it out.
  • Prints only the path on stdout; every other line goes to stderr.

Commit identity (core#2708)​

A linked worktree has no config of its own. It reads the main checkout's $GIT_COMMON_DIR/config, and — this is the part that bites — a plain git -C <worktree> config user.name X writes back to that shared file. Measured on the devbox 2026-08-31: one such call re-pointed the commit identity of all 211 live worktrees simultaneously.

So neither of the two obvious things works:

approachwhat actually happens
do nothing (the pre-#2708 behaviour)worktree inherits /opt/upsquad/upsquad-core's identity. It was upsquad-qa-engineer[bot], so every agent committed as QA.
git -C "$WT" config user.name …silently rewrites the shared config for every concurrent agent. Same bug, inverted.

The script does the third thing: extensions.worktreeConfig=true on the shared repo (one-time, idempotent) plus git config --worktree, which writes to $GIT_DIR/worktrees/<name>/config.worktree — genuinely per-worktree. It then reads the value back and asserts (a) it is the agent's, (b) it is served by that config.worktree and not the shared file, and (c) the shared checkout's user.name did not move. Any of those failing aborts before a path is printed.

Enabling extensions.worktreeConfig is a repo-wide setting, so it is worth knowing what it does and does not change. It makes git additionally read config.worktree where one exists. Every worktree without one behaves exactly as before, so the 211 pre-existing worktrees and the main checkout are unaffected until the script writes one for them. The two documented hazards are core.bare and core.worktree needing to move to the main worktree's config.worktree; on this repo core.bare=false and core.worktree is unset, so there is nothing to move.

Agent names are now closed-set. unknown, arch, architect-review, backend, or a typo are hard errors (exit 2, no worktree created) rather than a worktree that commits as somebody else. The seven canonical names and their GitHub identities live in scripts/lib/agent-identity.sh.

Never hand-write a bot's noreply email. The numeric prefix in <id>+<login>[bot]@users.noreply.github.com is what GitHub resolves the commit to; a wrong one renders the right name and links to no account, showing up as author: null on the API. Use the script, or source scripts/lib/agent-identity.sh.

Measured 2026-08-31, and always quote the population with the number — these two differ by ~125x and conflating them is an error that shipped once:

populationunlinked commitsdistinct fabricated ids
merged PR branches50546 numeric (63 bad emails)
main41 numeric, plus 3 × backend-sme@upsquad.bot

main is overwhelmingly squash-merged (~1.10 commits per merged PR) and a squash discards the branch commits' authorship, so most of the damage never lands. The branch figure measures how often agents get it wrong; the main figure measures permanent damage. See core#2710.

Guards: tests/scripts/test_agent_worktree_identity.sh (offline, in the Scripts Unit Tests lane) proves a worktree gets the identity its agent is owed; scripts/verify-agent-identities.py (nightly Agent Identity Audit) proves the table itself still matches the GitHub API — the offline suite structurally cannot, since a fabricated id satisfies every offline assertion.

Scratch files and the rest of the shared substrate (core#3240)​

The worktree isolates the checkout. It does not isolate the other three things an agent writes: the session scratchpad, a shared checked-in file, and the remote branch. All three have now produced the same failure — a green result for work that is not yours — which is the absence-scored-as-a-pass family with the subject swapped for somebody else's subject.

The session scratchpad is shared by every concurrent agent, not per-task​

Measured 2026-09-08, three incidents in one night:

incidentwhat happened
#3153the agent's mutate.py was overwritten by a #3152 sibling's. Re-running it printed a confident ALL-KILLED for the sibling's mutants. Caught by reading the output, not by the exit code — the exit code was 0 and correct
#3157lld3138.md replaced by a sibling's file, and pr.md deleted mid-task. One size discrepancy remains unexplained
#3139 (PR #3239)the mutation battery and the guard roll-call were run out of the scratchpad as mutants.py / tests.json. That directory held 2808 files with concurrent writers, and the roll-call input tests.json had already been deleted by something else before the lander checked. Nothing in the results looked wrong — "looked wrong" is not the failure mode

The lander on #3239 did not rename the files and move on; it re-ran both from a path only that task writes to, against a freshly generated stream, and reported that the numbers were unchanged and now reproducible. That is the standard: a scratchpad result is re-measured, not relabelled.

Where harness and artefact files go​

locationverdict
sibling directory to the worktree — /opt/upsquad-worktrees/<repo>/<agent>-<issue>-scratch/use this. Task-scoped by construction, outside the checkout, and it survives for post-mortem the same way the worktree does
inside the worktreeno. Every artefact is an untracked file, and you cannot exclude it: git resolves info/exclude to the common dir — the shared checkout's, which no agent may write — and a per-worktree $GIT_DIR/info/exclude is not consulted. Measured 2026-09-08: a pattern in .git/worktrees/<name>/info/exclude left the file ?? in git status; the same pattern in the common dir's info/exclude cleared it. Untracked files are also a KEEP condition for the reclaim sweep, so leftovers change that verdict too
the shared session scratchpadonly under task-scoped filenames — mutate-<issue>-<agent>.py, never mutants.py
/tmpnever. Stranger-owned stale files corrupted two mutation restores on 2026-09-07

Before trusting any re-run of a scratchpad script, verify its ROOT and ids are yours. The filename is not the evidence — #3153's file had the right name throughout.

A text-anchored graft into a shared file must assert its anchor unique on the CURRENT tree​

Recorded on PR #3239, then made worse by the act of recording it.

#3239 grafted a roster entry into scripts/check-named-test-guards.py on the anchor ROSTER: tuple[Guard, ...] = ( — which is a substring of the SQL_ROSTER declaration, so the file contains it twice. The graft asserted its anchor was unique, got 2, and stopped. Had it not, the entry would have landed in the SQL roster, evaluated in the db.yml lane, which cannot run a unit mutant — and both the roll-call and the count would have been green with the guard in a lane that could never fire it.

#3237 then found the naive anchor occurs three times on main, not two: the real roster, the SQL roster, and S1's own prose warning about the trap. Documenting a hazard added an instance of it. Re-verified at 9e04dd059:

230: ROSTER: tuple[Guard, ...] = ( <- the real one
1167: # ... `SQL_ROSTER: tuple[Guard, ...] = (` CONTAINS the unit roster's ...
1251: SQL_ROSTER: tuple[Guard, ...] = (

So: anchor on \nROSTER: tuple[Guard, ...] = ( — the leading newline is what stops SQL_ROSTER matching (3 → 1 occurrences) — assert count == 1 before writing, and verify placement after. A uniqueness assertion computed once and carried forward is worth nothing; the count moved on a docs commit.

--force-with-lease fails stale info after an inline-token fetch​

Worktree fetch and push go through an inline HTTPS token URL, not a named remote (the devbox remotes are SSH and bots have no SSH key). A URL is not a remote, so refs/remotes/origin/<branch> is never written for the branch — and the bare --force-with-lease takes its expected value from exactly that ref. With no ref there is no lease, and git refuses. Measured 2026-09-08:

! [rejected] topic -> topic (stale info)

stale info reads like a race and is not one — it is an absent tracking ref, not an outdated one. Nothing was concurrently pushed. Supply the lease explicitly instead, re-fetching the value in the same command rather than reconstructing it:

git push "https://x-access-token:${GH_TOKEN}@github.com/upsquad-ai/upsquad-core.git" \
--force-with-lease="$BRANCH:$(git ls-remote \
"https://x-access-token:${GH_TOKEN}@github.com/upsquad-ai/upsquad-core.git" \
"refs/heads/$BRANCH" | cut -f1)" \
"$BRANCH:$BRANCH"

Never fall back to bare --force. The lease is the only thing standing between a rebase and another agent's push to the same branch — #3188 took its lease against the reviewed head add7d75e precisely so a rebase could not clobber anything else.

Success criteria (from the issue)​

  • Two agents dispatched simultaneously on the same repo produce independent PRs with zero clobbering.
  • Setup overhead under 5 seconds per dispatch.
  • Documented convention in CLAUDE.md for every repo.

Measured during core#852 delivery:

$ time (AGENT_NAME=test-b bash scripts/agent-worktree.sh 9999 test-b &
AGENT_NAME=test-c bash scripts/agent-worktree.sh 10000 test-c &
wait)
# ...
real 0m1.800s

1.8 s for two parallel worktrees, well under the 5 s SLA. Separate AGENT_B_FILE / AGENT_C_FILE writes to each worktree confirmed isolation — git status in each showed only that worktree's own untracked file.

Cleanup​

Worktrees persist after the agent finishes. This is intentional: if an agent task failed, the next agent picking it up should see the last state rather than a fresh checkout.

A worktree is reclaimed when every commit reachable from its HEAD is already on origin — including under refs/pull/*/head, which is where a merged PR's commits live once GitHub deletes the branch — its tree is clean and readable, it is unlocked, no file in it has been modified for --min-untouched-hours (default 6), no PR that claims it is open, and its HEAD has not moved for --min-idle-days (default 14, or --merged-pr-idle-days (default 2) when the PR is merged):

# Run from the main checkout so the self-protection guard doesn't
# skip a worktree you're currently inside.
cd /opt/upsquad/upsquad-core
bash scripts/prune-agent-worktrees.sh --report # what is here and why (read-only)
bash scripts/prune-agent-worktrees.sh --dry-run # what would be removed
bash scripts/prune-agent-worktrees.sh # do it

The sweep authenticates over HTTPS with a bot token (scripts/lib/git-https-fetch.sh → gh-token.py), not SSH — the devbox remotes are git@github.com:... and bots have no SSH key, which is why the sweep silently never ran and ~150 worktrees accumulated before #1506. The same helper backs the fresh fetch in agent-worktree.sh, so worktree creation is clean for bots too. If that fetch fails the sweep aborts rather than reasoning from stale remote-tracking refs.

Why reachability and not "is the branch merged" (core#2751)​

The branch test was a proxy. Measured on the devbox 2026-09-01, it broke in three directions at once and the sweep reclaimed 0 on 1095 of its last 1197 runs:

defectconsequence
undefined for a detached HEAD31 review worktrees kept forever
trivially true for a NEW branch off origin/maina worktree was deleted 16 minutes after an agent created it
git ... 2>/dev/null || echo "" swallowed fatal: detected dubious ownership10 unreadable trees reported as "detached HEAD"; the sibling status probe reported them clean

git rev-list --count HEAD --not --remotes=origin == 0 needs no branch and answers the question that actually matters: would deleting this directory destroy anything that is not on origin.

It is MORE PERMISSIVE than "branch merged", not stronger. merged-into-main implies all-commits-on-origin; the converse is false. The counterexample is an open PR — branch pushed, not merged: old rule KEEP, new rule RECLAIM. 56 of the 62 worktrees it reclaims are exactly that case. What is lost is not commits (on origin by construction) but git-ignored state — build caches, .env, scratch notes. --min-idle-days is what replaces the old in-flight protection.

The direction that matters runs the other way. The old rule also removed on "branch no longer on origin", and could not tell deleted after merge (safe) from never pushed (not safe) — both give on_origin=0. Over the guard's whole log: 204 removals, 145 (71%) under that clause, versus 59 merged. 50 were the principal-architect's own review worktrees, and three mid-review disappearances previously blamed on "a concurrent sweeper" are named in the log — pr2739, pr2739b, pr2743, all never-pushed local review branches whose commits existed nowhere else. That is the real justification for the change.

Safety — all must hold, every one fail-closed. Any probe that errors makes the worktree unreadable, which is kept and reported as such rather than folded into another category. Kept if: locked, dirty, has untracked files, has any commit not on origin, has moved in the last --min-idle-days, or has no readable reflog. A stash is not per-worktree — refs/stash lives in the common dir — so removal never destroys one, and objects always survive in the shared object database.

The PR-head blind spot (core#3190) — why --remotes=origin was the wrong half​

--remotes=origin enumerates refs/remotes/origin/* and nothing else. Two facts about this repo turn that into a permanent false negative:

  1. main takes squash merges, so a merged PR's branch tip is never an ancestor of main. Ancestry is not a merged-detector here at all.
  2. GitHub deletes the head branch at merge, so the tracking ref is removed by the fetch --prune the sweep runs on purpose — permanently.

A worktree whose work landed months ago therefore reads unpushed forever. Measured 2026-09-10, after a hand-verified reclaim of 83 provably-landed trees had already been done by hand:

done: removed=0 ... unpushed=54 ... (sanctioned: unpushed=44)

removed=0, with 44 sanctioned trees blamed on "commits not on origin", while the box sat at 97% and an agent had to override agent-worktree.sh's 95% refusal to work at all. The false-positive rate on the trees #3190 examined was 153/155 = 98.7%.

The commits are on origin. GitHub retains refs/pull/<N>/head indefinitely for every PR, branch or no branch. One ls-remote reads all of them:

git ls-remote <origin> 'refs/pull/*/head' # 1328 refs, one round trip

Three things about how it is read, each got wrong first:

trapwhat happenswhat the sweep does
fetching into refs/remotes/origin/*not matched by remote.origin.fetch, so any concurrent agent's fetch --prune deletes it — observed mid-sweep 2026-09-07, after which 154 trees silently read unpushed againnever creates a local ref at all
re-reading refs per worktreethe decision set changes underneath a running sweepsnapshots to a file at startup, reads it with rev-list --stdin
^<sha> for an object we do not haverev-list diesfilters through cat-file --batch-check; a sha absent locally cannot be an ancestor of a local HEAD, so no verdict changes

--not is a toggle, not a per-argument negation: HEAD --not A --not B flips the sense back to positive and reported 2129 where the correct spelling reported 0. The --stdin form sidesteps it — every snapshot line carries its own literal ^.

Effect on the live registry, same box, same hour, stock vs fixed:

verdictstockfixed
unpushed5411
pristine933
in-flight (new)—2
touched (new)—7

The 11 survivors are genuine: architect merge-test trees carrying local-only commits that exist nowhere else. pristine grew, because trees formerly blocked at unpushed now reach the core#2751 gate — that protection is intact and now covers more.

What paid for widening it: two new gates and a scoped floor​

Widening the negative set makes the sweep more permissive, and --min-idle-days was carrying all of the in-flight protection. Now that PR heads are being read anyway, the real question is answerable:

  • in-flight — any PR that claims the worktree is OPEN ⇒ keep, unconditionally, at any idle age. Strictly more conservative than the old rule, which reclaimed open-PR worktrees once they went quiet (1 in 20 of everything it removed). Live on the devbox: PRs #3270 and #3349, both of which the stock rule was eligible to delete.

    "Any", not "the". A worktree resolves against every refs/pull/<N>/head matching its head SHA and every PR for its branch, unioned, then reduced with open-wins semantics. A first-match selector is a selector ordered by PR numbering — ls-remote sorts lexicographically by refname — and in a deleter a wrong selection is a deletion. Reviewed live on PR #3355: a first-match selector deleted an open-PR worktree (removed=5 in-flight=0).

    Branch reuse is routine here, not hypothetical: 1331 advertised refs/pull/*/head over 1328 distinct SHAs on this repo (2026-09-10), so three SHAs are claimed by two PRs each — #1758/#1759, #1728/#1729, #2057/#2058, all the "close it, re-PR as the next number" shape. Those three happen to fail safe today because the first match is the closed one. That is luck, not design.

    Ambiguity does not inherit merged status. The shortened floor is granted only when identification is unambiguous and merged — exactly one claimant, and it merged. Two claimants, or any claimant whose state could not be read, means the tree is not identified, and an unidentified tree keeps the full --min-idle-days however many of its claimants merged.

  • touched — any file in the tree modified in the last --min-untouched-hours (default 6) ⇒ keep. This is what makes a shorter floor safe: an agent can read, build and run tests for days without moving HEAD, and that activity is invisible to a reflog-derived age.

Only then does --merged-pr-idle-days (default 2) apply, and only to worktrees whose PR is merged: nothing is in flight, the commits are on origin, the tree is clean, HEAD has moved at least once, and nobody has touched a file in 6 hours. What is left to lose is git-ignored state. unknown and closed keep the full --min-idle-days — no positive evidence, no shortcut.

Every part of it is fail-closed, and the header line says which parts ran:

decision set: origin-tips=362 pr-heads=ok(advertised=1328 usable=1328) \
pr-state=ok freshness=ok(6h) idle-floor=14d merged-pr-floor=2d

An unreadable PR-head list (ls-remote-failed, zero-advertised, filter-failed), an unavailable gh (--no-pr-state, gh-unauthenticated), or a find that cannot evaluate the freshness probe each clamp --merged-pr-idle-days back up to --min-idle-days and are named in that line. A sweep that quietly reverted to the old behaviour and logged a clean run is the failure this repo keeps re-learning.

The freshness probe self-checks, and here is why​

find on this devbox is bfs, which rejects relative timestamps. -newermt '-6 hours' errors on every path, so the gate matches nothing — it reported 0 of 175 worktrees as fresh on a box running five agents, i.e. it would have scored a tree in active use as safe to delete. It was caught only because "zero active worktrees" was implausible, not by anything in the code.

So the cutoff is an absolute ISO stamp, and at startup the gate is fired at a file created milliseconds earlier. If that does not match, freshness=inert and the merged-PR shortcut is switched off. The test battery pins this with a find shim that exits 0 with no output — the silent variant, the only one the per-tree exit-status check cannot catch.

Any freshness check written here must use an absolute stamp and be probed against a known-fresh path. Absolute stamp alone is not the lesson; the probe is.

The pristine window (core#2751)​

Every agent worktree is deletable from creation until its first commit. A tree fresh out of git worktree add ... origin/main has HEAD at the tip of origin/main, which is an ancestor of origin/main, so the old rule called it merged and removed it. Not a naming problem, not a timing problem — a property of every worktree's initial state, with nothing standing in the way but whether a file happened to have been written when the sweep fired. The sweep fires every 15 min. Three observed incidents: this author's worktree at 16 minutes old, 50 architect worktrees in the log (pr2739, pr2739b, pr2743 mid-review), and a third agent watching its worktree vanish mid-session the same day.

So there is an unconditional gate, evaluated before the idle gate: a worktree whose HEAD reflog holds exactly one distinct value — HEAD has never moved since creation — is always kept, and reported as pristine.

--min-idle-days cannot express this. Idle age is derived from when HEAD last moved; in the pristine state it never has, so the number measures the worktree's age, not the agent's activity — an agent can read, build and edit ignored files for days without moving HEAD. Raising the threshold narrows the window; nothing expressible in terms of it closes the window.

Two obvious spellings are inert — both were tried and measured:

spellingmatched
HEAD == origin/main0 of 208 — origin/main advances, so a tree created at yesterday's tip stops matching
reflog has exactly one line0 of the trees the real creation path makes — git worktree add -b writes two lines at creation, 0000->X then X->X
distinct new-SHAs == 170 of 208 — the property, immune to git's bookkeeping

Cost: 4 worktrees move from reclaim to pristine (179 MB of 3.58 GB); 58 are still reclaimed. A pristine tree is byte-identical to a fresh checkout, so a human who has eyeballed one loses nothing by removing it with git worktree remove.

Age comes from the reflog, never from mtime. git status rewrites the index, so any tool that inspects a worktree resets an mtime-based age — including this sweep, on every tree, every 15 minutes.

Scope​

The registry is not confined to /opt/upsquad-worktrees/<repo>: on the devbox 101 of 210 registered worktrees live under the Agent SDK's .claude/worktrees/ root, /tmp, or a legacy /opt/upsquad/worktrees. The sweep inspects and reports all of them, and removes only in the classes named by --remove-roots (default sanctioned). Widening removal is a reviewed decision, not a side effect of fixing the reporting.

How it is actually scheduled​

worktree-reclaim.timer / worktree-reclaim.service (Sundays 03:30 UTC) are checked in but not installed on the devbox — verified 2026-09-01, zero journal entries in 90 days. What actually runs the sweep is scripts/dev-disk-guard.sh, installed under vb's systemd user manager and firing every 15 minutes whenever the disk is >= 85%. systemctl --user as another user cannot see it, which is how "the timer does not exist" and "the sweep runs 90x/day" both look true. Check the log, not the unit:

tail -40 /var/tmp/dev-disk-guard.log
cat /var/tmp/dev-disk-guard.status

Disk headroom​

scripts/agent-worktree.sh prints a disk: line on every dispatch — used %, free, and the disk-guard marker's state and age — warns at 85%, and refuses outright at 95% (override: AGENT_WORKTREE_ALLOW_LOW_DISK=1). Running the box out of disk mid-task does not present as "no space left"; it presents as unrelated tool errors, and diagnosing that costs far more than being stopped at the start. The guard's own webhook has never been configured, so its 449 CRIT events went unread — see #2751.

Relationship to Claude Code Agent SDK isolation: "worktree"​

The SDK's built-in isolation: "worktree" param gives subagent tool calls their own worktree for the duration of the call. It is complementary, not a replacement, for the filesystem convention documented here:

  • SDK isolation covers subagent tool calls that modify the repo. Agents SHOULD pass isolation: "worktree" when spawning a subagent that might git checkout or write files.
  • Filesystem convention covers human-invoked, cron-invoked, and top-level agent dispatches. Without it, any of those contexts can still clobber an SDK-isolated subagent's parent checkout.

When in doubt, prefer the filesystem convention — it works uniformly across every invocation path.

References​

  • scripts/agent-worktree.sh — dispatch helper.
  • scripts/prune-agent-worktrees.sh — the reclaim sweep (run by the disk guard, not weekly).
  • scripts/lib/git-https-fetch.sh — shared HTTPS bot-token fetch helper.
  • scripts/systemd/worktree-reclaim.{service,timer} — weekly disk reclaim. Not installed on the devbox.
  • scripts/dev-disk-guard.sh — what actually invokes the sweep, every 15 min at >= 85% used. Since #3508 it trims the Go build cache BEFORE the sweep (cheapest and most reversible first) and reports what it could not reclaim; the sweep still runs unconditionally, because it is the only thing bounding worktree count. Details: docs/runbooks/devbox.md → Resource guards.
  • tests/scripts/test_prune_agent_worktrees.sh — the reclaim-policy regression guard.
  • tests/scripts/test_dev_disk_guard_gocache.sh — the regression guard for the Go build-cache trim (#3508).
  • CLAUDE.md → "Parallel agent dispatches (MANDATORY)" section.
  • core#852 — the task that produced this convention.
  • core#1506 — SSH→HTTPS auth fix + scheduled reclaim.
  • core#2751 — reachability-based reclaim, all-roots reporting, disk preflight.
  • core#3240 — the shared-scratchpad hazard, the sibling-directory fix pattern, and the two later refinements (anchor uniqueness on the current tree; the lease after an inline-token fetch). Refs core#3232, PR #3237, PR #3239, core#3188.