The field is edited far more often than it should be, and every edit is a hand-copy into a UI with no review and no gate — the exact surface #85 was filed about.
Why it is tedious, precisely
The field pins three volatile files. session-start-dispatch.mjs changed twice on 2026-08-01 alone. Every such change moves PIN and at least one SUM_*, which means:
The tedium is not incidental. It follows from what is pinned: pinning exact bytes requires updating the pin whenever the bytes change. No amount of restructuring escapes that while the pinned artifacts are the ones under active development.
The move
Pin something stable instead, and let it obtain the volatile part by another mechanism.
field ──pins by digest──▶ .claude/bootstrap.sh ──obtains──▶ dispatcher, register-mcp, stop-hook
(stable, ~rarely (in repo: (volatile — changes
changes) reviewed, tested, whenever we work on them)
gated)
The field becomes a loader of roughly fifteen lines, every value read from an environment variable with a working default:
#!/usr/bin/env bash
# bounded-systems bootstrap LOADER. Do not add logic here — see .claude/README.md.
set -uo pipefail
ROOT="${BOOT_ROOT:-/home/user}"
REPO="${BOOT_REPO:-bounded-systems/.github}"
REF="${BOOT_REF:-main}"
WANT="${BOOT_SHA256:-}"
LOCAL="$ROOT/.github/.claude/bootstrap.sh"
[ -f "$LOCAL" ] && exec bash "$LOCAL" # attached checkout always wins
[ -n "$WANT" ] || { echo "bootstrap: .github not attached and no BOOT_SHA256 — refusing"; exit 0; }
tmp="$(mktemp)"
curl -fsSL --retry 2 "https://raw.githubusercontent.com/$REPO/$REF/.claude/bootstrap.sh" -o "$tmp" || exit 0
echo "$WANT $tmp" | sha256sum -c - >/dev/null 2>&1 \
|| { echo "bootstrap: REFUSING bootstrap.sh — sha256 mismatch"; rm -f "$tmp"; exit 0; }
exec bash "$tmp"
BOOT_SHA256 moves only when bootstrap.sh itself moves. Everything volatile is fetched by bootstrap.sh, which is version-controlled — so CI can maintain it, and no human retypes anything.
The one real decision
bootstrap.sh must obtain three volatile files. It cannot carry their digests, because it is fetched at $BOOT_REF and digests fetched alongside the files they describe verify nothing — the argument .claude/README.md already makes. So how it obtains them is a trust choice:
|
field edits |
what the fetched path trusts |
| A. today — pin exact bytes of all three |
every .claude/ change |
reviewed bytes |
B. git + signature — clone at $BOOT_REF, verify the tip commit's signature against a fingerprint in an env var |
only when bootstrap.sh changes |
the org's merge identity, plus git's hash chain |
| C. git only — clone and trust the transport |
same as B |
GitHub + TLS |
B is the recommendation. It matches the org's stated posture — privileged effects are verified against a signed owner — and it is the same integrity the attached checkout already relies on, which .claude/README.md explicitly accepts:
The attached checkout is NOT digest-checked: it arrives over the session's git proxy with git's own integrity.
Verified 2026-08-03: commits on main do carry signatures (git log %G? → E, i.e. present but unverifiable without the key locally). Being squash merges, they are signed by GitHub's web-flow key rather than by a person — so B really asserts "merged through GitHub by someone with write access," not "authored by a named human." That is weaker than it sounds and should be stated plainly; it is nonetheless the same thing the merge gate already relies on, and strictly stronger than C.
What B gives up: the fetched path stops being "these exact reviewed bytes" and becomes "whatever main is." A bad merge propagates to fallback sessions immediately instead of waiting for a conscious pin bump. Given the fallback only runs when .github is not attached — a path .claude/README.md notes nobody exercises — that is a small exposure, but it is a real one and the call belongs to whoever owns the org's security posture.
What B deletes
Not a small refactor — a net simplification:
Roughly 500 lines of machinery whose entire job is maintaining a pin that would no longer exist.
Cost
One last field paste. After that the field is write-once, and BOOT_SHA256 changes only when bootstrap.sh does.
Related
#85 (restore the field — do that first regardless; this issue changes what gets pasted next time), #91 (the manifest; independent, and bootstrap.sh is its natural home), #92, #93.
The field is edited far more often than it should be, and every edit is a hand-copy into a UI with no review and no gate — the exact surface #85 was filed about.
Why it is tedious, precisely
The field pins three volatile files.
session-start-dispatch.mjschanged twice on 2026-08-01 alone. Every such change movesPINand at least oneSUM_*, which means:pinjob cannot push its bump branch — the broker'sfront-deskentry never requestedcontents, and the scope assertion did not check for it #87 blocks the automated one), andThe tedium is not incidental. It follows from what is pinned: pinning exact bytes requires updating the pin whenever the bytes change. No amount of restructuring escapes that while the pinned artifacts are the ones under active development.
The move
Pin something stable instead, and let it obtain the volatile part by another mechanism.
The field becomes a loader of roughly fifteen lines, every value read from an environment variable with a working default:
BOOT_SHA256moves only whenbootstrap.shitself moves. Everything volatile is fetched bybootstrap.sh, which is version-controlled — so CI can maintain it, and no human retypes anything.The one real decision
bootstrap.shmust obtain three volatile files. It cannot carry their digests, because it is fetched at$BOOT_REFand digests fetched alongside the files they describe verify nothing — the argument.claude/README.mdalready makes. So how it obtains them is a trust choice:.claude/change$BOOT_REF, verify the tip commit's signature against a fingerprint in an env varbootstrap.shchangesB is the recommendation. It matches the org's stated posture — privileged effects are verified against a signed owner — and it is the same integrity the attached checkout already relies on, which
.claude/README.mdexplicitly accepts:Verified 2026-08-03: commits on
maindo carry signatures (git log %G?→E, i.e. present but unverifiable without the key locally). Being squash merges, they are signed by GitHub'sweb-flowkey rather than by a person — so B really asserts "merged through GitHub by someone with write access," not "authored by a named human." That is weaker than it sounds and should be stated plainly; it is nonetheless the same thing the merge gate already relies on, and strictly stronger than C.What B gives up: the fetched path stops being "these exact reviewed bytes" and becomes "whatever
mainis." A bad merge propagates to fallback sessions immediately instead of waiting for a conscious pin bump. Given the fallback only runs when.githubis not attached — a path.claude/README.mdnotes nobody exercises — that is a small exposure, but it is a real one and the call belongs to whoever owns the org's security posture.What B deletes
Not a small refactor — a net simplification:
gen-bootstrap-pin.mjsandbootstrap-pin.test.mjs(the pin/digest generator and its drift gate)pinjob inorg-defaults.yml, and the bump PRs it exists to openpinjob cannot push its bump branch — the broker'sfront-deskentry never requestedcontents, and the scope assertion did not check for it #87's urgency: with no bump branch to push, onlyregistry-graph.ymlstill needscontents: writeRoughly 500 lines of machinery whose entire job is maintaining a pin that would no longer exist.
Cost
One last field paste. After that the field is write-once, and
BOOT_SHA256changes only whenbootstrap.shdoes.Related
#85 (restore the field — do that first regardless; this issue changes what gets pasted next time), #91 (the manifest; independent, and
bootstrap.shis its natural home), #92, #93.