The in-repo home for .github-private#848, which carries the original report and the reproduction. boot.sh lives here, so the fix does.
The defect
9: set -uo pipefail ← no -e
90: BOOT=/opt/bounded-boot
91: mkdir -p "$BOOT" ← no sudo, no fallback, return value never checked
209: echo "bootstrap: ready — dispatcher at $BOOT" ← unconditional
.github not attached → the fetch branch is taken → /opt is root-owned → mkdir -p fails → every fetch_verified fails writing into a directory that does not exist → 0 of 7 artifacts installed, no settings.json, and the script prints ready and exits 0.
Why nothing caught it
That branch had only ever run as root. A managed session runs as root, so /opt was writable there and nowhere else. The branch exists specifically for hosts that are not managed sessions, and every one of those is the case it was never exercised in.
Measured on the first real floor — an exe.dev VM, user exedev, 2026-09-02 — then reproduced under controlled conditions the same day:
| case |
repos |
user |
/opt |
installed |
exit |
| C |
none |
uid 1000 |
root-owned |
0 of 7 |
0 |
| B |
none |
root |
root-owned |
7 of 7 |
0 |
| D |
none |
uid 1000 |
pre-created writable |
7 of 7 |
0 |
Note the exit column: 0 in every case, including the empty one. The status carries no information in either direction, which is worse than the issue first stated.
The fix
- Fall back to a user-writable cache.
/opt stays preferred — a shared host cache is right where one exists — then ${XDG_CACHE_HOME:-$HOME/.cache}/bounded-boot.
- Test writability, not the
mkdir status. A root-created /opt/bounded-boot makes mkdir -p succeed for an unprivileged caller while every write into it still fails — the same silent-empty-floor outcome by a different route.
- Refuse when nowhere is writable, rather than proceeding to fail six fetches.
- Stop printing
ready unconditionally. Accumulate what failed and exit non-zero naming it.
Verified as a matrix: fallback installs 7 of 7 with settings.json written; nothing writable refuses with exit 1; a writable cache with broken fetches reports INCOMPLETE naming all six and exits 1; a writable /opt is still preferred; an attached checkout still never takes the fetch branch at all.
And a test, so the environment is covered rather than just the code
.claude/boot-cold-start.test.sh, wired into org-defaults.yml. It builds a genuine bare host with unshare -Urm — no privilege required — and refuses rather than skipping where user namespaces are unavailable, because a cold-start check that quietly skips reports the same green as one that passed.
Two properties were measured before being relied on:
- a tmpfs over
/home/user is required: setting CLAUDE_SESSION_ROOT alone does nothing, because ROOT's probe falls past an empty root to a hardcoded /home/user. No test can simulate a bare host by environment variable.
- inside the namespace the caller is uid 0 yet
/opt stays unwritable, since it is owned by an unmapped uid — so the failure reproduces without root. /opt is masked with a read-only mount, not a chmod: namespace-root holds CAP_DAC_OVERRIDE and would write straight through mode bits. Caught by running the suite as root, where two cases passed for the wrong reason with artifacts quietly landing in /opt.
Two edits outside boot.sh, both load-bearing
exit added to FIELD_PLUMBING in gen-bootstrap-pin.mjs. exit reports a verdict and installs nothing; without it the parse rejects the very line that stops a silent no-op floor. The parser's own error message sanctions exactly this ("If it stages or reports, add its verb to FIELD_PLUMBING with a reason").
The fallback is written as if blocks, not guard-or-assign one-liners. session-start-dispatch.test.mjs resolves this file's variables to check where the dispatcher is installed from, and reads a guard followed by an or-assignment as a probe chain whose last value is canonical — correct for ROOT, whose last fallback is the historical layout. Here the first value is canonical and the rest is error handling, so the one-liner spelling would tell that gate the dispatcher lives under the home directory: precisely the confusion #69 exists to catch.
Worth recording because it bit twice: that resolver scans comments, with a pattern not anchored to line start. A draft comment explaining why the one-liner form is avoided spelled it out literally and thereby reassigned BOOT to its own prose. The failure message read boot.sh installs the dispatcher from …,/session-start-dispatch.mjs`.
Also fixed by this, incidentally
.github-private#849 observes that verb-server.mjs could not be fetched at all on the v7 payload. v8 already fetches six artifacts including it; this changes none of that, and the count is 7 files installed (six artifacts plus a generated .mcp.json), not six.
Checks
node --test .claude/*.test.mjs → 277/277 · node --test *.test.mjs → 276/276 · bash .claude/boot-cold-start.test.sh → 10/10 · gen-bootstrap-pin --check → ok.
Related: .github-private#848 (report + reproduction) · .github-private#849 (same-origin trust anchor) · .github-private#815 (the floor this was found raising) · #91/#85 (why the step parse refuses unknown verbs) · #69 (the $HOME confusion the resolver guards)
🤖 Generated with Claude Code
https://claude.ai/code/session_01HgvhK8cfkWF3awpg6gc6tL
The in-repo home for
.github-private#848, which carries the original report and the reproduction.boot.shlives here, so the fix does.The defect
.githubnot attached → the fetch branch is taken →/optis root-owned →mkdir -pfails → everyfetch_verifiedfails writing into a directory that does not exist → 0 of 7 artifacts installed, nosettings.json, and the script printsreadyand exits 0.Why nothing caught it
That branch had only ever run as root. A managed session runs as root, so
/optwas writable there and nowhere else. The branch exists specifically for hosts that are not managed sessions, and every one of those is the case it was never exercised in.Measured on the first real floor — an exe.dev VM, user
exedev, 2026-09-02 — then reproduced under controlled conditions the same day:/optNote the exit column: 0 in every case, including the empty one. The status carries no information in either direction, which is worse than the issue first stated.
The fix
/optstays preferred — a shared host cache is right where one exists — then${XDG_CACHE_HOME:-$HOME/.cache}/bounded-boot.mkdirstatus. A root-created/opt/bounded-bootmakesmkdir -psucceed for an unprivileged caller while every write into it still fails — the same silent-empty-floor outcome by a different route.readyunconditionally. Accumulate what failed and exit non-zero naming it.Verified as a matrix: fallback installs 7 of 7 with
settings.jsonwritten; nothing writable refuses with exit 1; a writable cache with broken fetches reportsINCOMPLETEnaming all six and exits 1; a writable/optis still preferred; an attached checkout still never takes the fetch branch at all.And a test, so the environment is covered rather than just the code
.claude/boot-cold-start.test.sh, wired intoorg-defaults.yml. It builds a genuine bare host withunshare -Urm— no privilege required — and refuses rather than skipping where user namespaces are unavailable, because a cold-start check that quietly skips reports the same green as one that passed.Two properties were measured before being relied on:
/home/useris required: settingCLAUDE_SESSION_ROOTalone does nothing, becauseROOT's probe falls past an empty root to a hardcoded/home/user. No test can simulate a bare host by environment variable./optstays unwritable, since it is owned by an unmapped uid — so the failure reproduces without root./optis masked with a read-only mount, not achmod: namespace-root holdsCAP_DAC_OVERRIDEand would write straight through mode bits. Caught by running the suite as root, where two cases passed for the wrong reason with artifacts quietly landing in/opt.Two edits outside boot.sh, both load-bearing
exitadded toFIELD_PLUMBINGingen-bootstrap-pin.mjs.exitreports a verdict and installs nothing; without it the parse rejects the very line that stops a silent no-op floor. The parser's own error message sanctions exactly this ("If it stages or reports, add its verb to FIELD_PLUMBING with a reason").The fallback is written as
ifblocks, not guard-or-assign one-liners.session-start-dispatch.test.mjsresolves this file's variables to check where the dispatcher is installed from, and reads a guard followed by an or-assignment as a probe chain whose last value is canonical — correct forROOT, whose last fallback is the historical layout. Here the first value is canonical and the rest is error handling, so the one-liner spelling would tell that gate the dispatcher lives under the home directory: precisely the confusion #69 exists to catch.Worth recording because it bit twice: that resolver scans comments, with a pattern not anchored to line start. A draft comment explaining why the one-liner form is avoided spelled it out literally and thereby reassigned
BOOTto its own prose. The failure message readboot.sh installs the dispatcher from …,/session-start-dispatch.mjs`.Also fixed by this, incidentally
.github-private#849 observes thatverb-server.mjscould not be fetched at all on the v7 payload. v8 already fetches six artifacts including it; this changes none of that, and the count is 7 files installed (six artifacts plus a generated.mcp.json), not six.Checks
node --test .claude/*.test.mjs→ 277/277 ·node --test *.test.mjs→ 276/276 ·bash .claude/boot-cold-start.test.sh→ 10/10 ·gen-bootstrap-pin --check→ ok.Related:
.github-private#848 (report + reproduction) ·.github-private#849 (same-origin trust anchor) ·.github-private#815 (the floor this was found raising) · #91/#85 (why the step parse refuses unknown verbs) · #69 (the$HOMEconfusion the resolver guards)🤖 Generated with Claude Code
https://claude.ai/code/session_01HgvhK8cfkWF3awpg6gc6tL