Context
Plan 00109 (v3.14.0+) collapsed the skill scripts/upgrade.sh to a thin curl-and-exec shim that fetches the canonical scripts/upgrade.sh from main HEAD and execs it. This matches the established hot-patch flow already used by version_check.py's upgrade suggestion:
curl -fsSL https://raw.githubusercontent.com/.../main/scripts/upgrade.sh -o /tmp/upgrade.sh
less /tmp/upgrade.sh
bash /tmp/upgrade.sh
The user confirmed during plan drafting that this pattern stays because freezing logic at release time has already caused two SEV-class incidents (v3.9.x write-venv-metadata field bug; v3.10.0 print_info stdout-corruption hotfix). Hot-patchability is a hard requirement.
Open Questions for Long-Term Review
Pull-from-main HEAD has known tradeoffs that this issue tracks for future revisit:
-
No verification. A user pulling mid-release could get an inconsistent script if scripts/upgrade.sh is updated and pushed to main without an accompanying tag.
- Mitigation today: release process should hold
scripts/upgrade.sh changes for the same PR as the tag (informal — not currently enforced by CI).
-
No reproducibility. Two users invoking /hooks-daemon upgrade minutes apart can get different versions of the upgrade script. For an upgrade flow that should be deterministic per from_version → to_version pair, this is uncomfortable.
-
Alternative: releases/latest/download/upgrade.sh with sha256 verification against bootstrap-checksums.txt (the pattern the three sibling scripts — daemon-cli.sh, health-check.sh, init-handlers.sh — currently use). Verified but frozen-at-release, re-introducing the failure mode that motivated this refactor.
-
Alternative: Pinned tag with periodic refresh. A middle ground — pull from a fixed ref like latest-stable that's updated server-side after each release passes a soak period. Verified-ish (still freshness-controlled), but adds release-process complexity.
-
Alternative: Two-channel pull. Default to main, fall back to releases/latest/download/upgrade.sh on fetch failure. Hedges against main being momentarily broken (mitigates risk 1) but doubles the fetch logic.
Decision Point
Revisit when:
- (a) The sibling-script thinning plan lands (then all four entry points share the same fetch pattern and a unified decision can be made), OR
- (b) A mid-release inconsistency actually bites a user (then risk 1 has moved from theoretical to observed).
Until either trigger, the env override HOOKS_DAEMON_UPGRADE_REF (default main) is the lightweight hedge — paranoid users can pin to a tag manually.
Related
- Plan 00109:
CLAUDE/Plan/Completed/00109-skill-thin-shim-and-atomic-upgrade-commit/PLAN.md (after this plan ships)
- Plan 00104, Plan 00105: introduced + extended self-bootstrap to all four scripts
CLAUDE/development/RELEASING.md Step 14: documents the inert-but-published upgrade.sh artifact
Context
Plan 00109 (v3.14.0+) collapsed the skill
scripts/upgrade.shto a thin curl-and-exec shim that fetches the canonicalscripts/upgrade.shfrommainHEAD and execs it. This matches the established hot-patch flow already used byversion_check.py's upgrade suggestion:The user confirmed during plan drafting that this pattern stays because freezing logic at release time has already caused two SEV-class incidents (v3.9.x
write-venv-metadatafield bug; v3.10.0print_infostdout-corruption hotfix). Hot-patchability is a hard requirement.Open Questions for Long-Term Review
Pull-from-
mainHEAD has known tradeoffs that this issue tracks for future revisit:No verification. A user pulling mid-release could get an inconsistent script if
scripts/upgrade.shis updated and pushed tomainwithout an accompanying tag.scripts/upgrade.shchanges for the same PR as the tag (informal — not currently enforced by CI).No reproducibility. Two users invoking
/hooks-daemon upgrademinutes apart can get different versions of the upgrade script. For an upgrade flow that should be deterministic perfrom_version → to_versionpair, this is uncomfortable.Alternative:
releases/latest/download/upgrade.shwith sha256 verification againstbootstrap-checksums.txt(the pattern the three sibling scripts —daemon-cli.sh,health-check.sh,init-handlers.sh— currently use). Verified but frozen-at-release, re-introducing the failure mode that motivated this refactor.Alternative: Pinned tag with periodic refresh. A middle ground — pull from a fixed ref like
latest-stablethat's updated server-side after each release passes a soak period. Verified-ish (still freshness-controlled), but adds release-process complexity.Alternative: Two-channel pull. Default to
main, fall back toreleases/latest/download/upgrade.shon fetch failure. Hedges againstmainbeing momentarily broken (mitigates risk 1) but doubles the fetch logic.Decision Point
Revisit when:
Until either trigger, the env override
HOOKS_DAEMON_UPGRADE_REF(defaultmain) is the lightweight hedge — paranoid users can pin to a tag manually.Related
CLAUDE/Plan/Completed/00109-skill-thin-shim-and-atomic-upgrade-commit/PLAN.md(after this plan ships)CLAUDE/development/RELEASING.mdStep 14: documents the inert-but-publishedupgrade.shartifact