Skip to content

Make the setup-script field write-once: pin a stable loader, not three volatile files #96

Description

@bdelanghe

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status
    Todo

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions