fix(catalog): release-plugin.mjs requires the push-approval token before it pushes

pre-push-gate.sh is a text-matching PreToolUse hook and cannot see a `git push`
issued via execFileSync inside this script's own process — pinned as GAP in the
gate's header, with this script named as the concrete case (a plugin tag left the
machine unseen, 2026-09-12). --create-tag --write and --push now each require the
same one-shot push-approval token the gate checks, and consume it themselves after
a push succeeds, since post-push-consume.sh never fires for a call the gate never
saw. Tag-push and catalog-push share one token — one publish from the operator's
perspective. Red-first: 9 new tests (26 -> 35 in release-plugin.test.mjs, 0 fail
before implementation existed as an import error, 152/152 across the suite after).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-09-12 23:20:54 +02:00
commit ea9bf7ab02
3 changed files with 174 additions and 5 deletions

View file

@ -73,6 +73,17 @@ their own Forgejo repositories under `https://git.fromaitochitta.com/open/`.
consistent version, `check-versions.mjs` is green by construction. Never hand-edit a `ref` or a
README label for a release — use this. Pure planner + label reconciler + pre-flight/write step
covered by `scripts/release-plugin.test.mjs`.
- **`--create-tag --write` and `--push` each require the operator's push-approval token FIRST**
(Q3, decided 2026-09-12): `~/.claude/hooks/pre-push-gate.sh` matches `git push` in command
text and cannot see a push this script issues via `execFileSync` inside node — the script
mints+pushes a plugin tag and pushes the catalog itself, both invisible to that gate. So
before either push, run:
`mkdir -p ~/.claude/runtime/push-approvals && touch "~/.claude/runtime/push-approvals/$(pwd | sed 's|/|_|g')"`
(`pwd` must be this catalog directory — tag-push and catalog-push share ONE token, one
publish from the operator's perspective). The script consumes the token itself right after
a push succeeds, the same way `post-push-consume.sh` does for a direct push; a failed push
leaves the token intact for the retry. Covered by `pushAuthorisation`/`requirePushAuthorisation`/
`pushWithToken`/`consumeToken` in `scripts/release-plugin.test.mjs`.
- **Pre-flight gate (`--write` runs `check-versions` BEFORE it writes):** the helper calls `runGate()`
first and aborts with exit 1 — **nothing written** — if ANY plugin is ERROR, not just the one being
released (`check-versions`' exit code is catalog-wide). Previously the gate ran *after* both writes,