chore(tooling): merge-train skill + pre-merge gh-pr-merge verification hook#193
Merged
Conversation
…n hook Two coupled merge-workflow tooling deliverables: #177 — a PreToolUse guard (`.claude/hooks/premerge-verify.sh`, wired into the Bash matcher in `.claude/settings.json`) that mechanises the #119 near-miss LAW: before a `gh pr merge` it DENIES when the PR head.sha != the live branch-ref tip (dropped `synchronize` webhook → stale green checks) or when zero check-runs exist for that exact SHA, and ASKS (never hard-blocks) on any case it cannot verify (fork/deleted branch, API/auth failure, unparseable command). Decision logic is a pure `decide()` function with an offline `--selftest` matrix; poll commands foreground-tested before arming (CLAUDE.md poll-loop gotcha). #176 — `.claude/skills/merge-train/SKILL.md`, the main-session serial develop merge loop: arm a check-runs Monitor, run the pre-merge LAW, merge, re-sync the next PR via update-branch, plus the #94 504-reconcile and #119 stale-SHA rerun procedures. Validated with skill-creator quick_validate.py ("Skill is valid!"). Neither is user-visible app behaviour → no CHANGELOG entry. Closes #176 Closes #177 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
DocGerd
commented
Jul 24, 2026
DocGerd
commented
Jul 24, 2026
…runs API failure Self-review findings on PR #193: - Chained `gh pr merge A && gh pr merge B`: the greedy sed extracted only the last PR, so a fresh+green B silently vouched for an unverified A. Now: >1 `gh pr merge` in the command -> emit_ask (merges must be serial anyway). - The check-runs API call collapsed a failed call into COUNT=0 -> hard deny with a false "no check-runs exist" reason, contradicting the script's own ask-on-API-failure contract. Now a non-zero `gh api` exit -> emit_ask; only a successful 0 denies (matching the TIP_SHA lookup's degrade-to-ask behavior). Verified: --selftest OK; chained -> ask; single live merge -> allow; failed check-runs call -> ask branch; successful zero -> deny path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… string as data The substring match fired the guard whenever a command merely CONTAINED the phrase "gh pr merge" — so commit messages, printf/echo, and grep patterns that mention it (including this feature's own commits and tests) spammed false approval prompts. Now the hook anchors on the command actually BEGINNING with `gh pr merge` (after leading whitespace / env-var / sudo / time prefixes). Data-carrying commands begin with git/printf/grep and pass through untouched. The chained-merge `ask` now counts only segments that themselves start with the invocation, so a trailing `&& git commit -m '... gh pr merge ...'` is ignored. PR number is extracted from the invocation segment (up to the first separator), not greedily from the whole string. Tradeoff: a compound `git push && gh pr merge N` is no longer guarded (first token is git) — acceptable since the merge workflow runs the merge as its own command, and false-positive spam erodes the guard's usefulness. Verified via regression matrix: heredoc/printf/grep/echo -> pass-through; real single + env-prefixed merge -> verify; chained -> ask; selftest OK. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Codifies the hand-driven merge lifecycle into repo-committed automation. Both are
.claude/tooling only — no app code, no user-visible behaviour, so no CHANGELOG entry.#177 — pre-merge verification hook
.claude/hooks/premerge-verify.sh, wired as a thirdPreToolUsehook in theBashmatcher of.claude/settings.json. Mechanises the memorised #119 LAW on everygh pr merge(mine and the user's):denywhen the PR object'shead.sha!= the live branch-ref tip (GitHub dropped asynchronizewebhook → the green checks describe a stale commit — the exact fix(pwa): dodge Pages CDN gzip-of-range on the basemap archive (#118) #119 failure) or when zero check-runs exist for that exact SHA.ask(never a hard block) on anything it cannot verify: fork/deleted branch (ref 404), API/auth failure, unparseable command, no resolvable PR. Hook fragility can never block a legitimate merge.gh pr mergecommand.Decision logic is a pure
decide()function with an offline--selftestmatrix (6 cases, green). Poll commands were foreground-tested against the real API before arming (CLAUDE.md poll-loop gotcha). Live production-path tests: non-merge → pass;gh pr view→ not triggered; compoundgit push && gh pr merge N→ PR# parsed; merged-PR/deleted-branch → gracefulask.#176 — merge-train skill
.claude/skills/merge-train/SKILL.md— the main-session serial develop merge loop (a skill, not an agent: the watcher agent is what oversleeps). Encodes: arm a check-runs Monitor → pre-mergehead.sha/check-runs LAW (now backstopped by the #177 hook) → merge-commit →update-branchre-sync of the next serial PR, plus the #94 504-reconcile and #119 stale-SHA rerun procedures, and the Projects-classic REST fallbacks. Validated with skill-creatorquick_validate.py→Skill is valid!. Model-invocable (assistant drives develop merges); the user-onlydevelop→mainrelease path stays in the separatereleaseskill.Closes #176
Closes #177
🤖 Generated with Claude Code