--- key: release.merge-consent-autogrant title: 2026-07-12 — Merge-consent gate auto-grants when satisfied (no redundant prompt); state IS the consent (#314) status: active since: '2026-07-12' supersedes: none superseded-by: none rule: 'When Done-when boxes are ticked, CI is green, and a fresh positive Review-verdict references head, the merge-consent hook emits `permissionDecision: allow` to actually suppress the redundant mechanical prompt — the derived state IS the consent, no separate conversational confirmation on that path.' signals: 'auto-grant, permissionDecision allow, merge consent · paths: `.claude/settings.json` · issues: #314, #303, #317' mechanics: '`pretooluse-merge-consent.sh`; CLAUDE.md → Task Completion Protocol' --- Completes the #303 H6/H10 intent — *derive merge-consent from state* — which the original hook only half-delivered. The rule the user set: **merge permission is auto-granted for the session when the linked issue's `## Done-when` boxes are all ticked, a fresh positive `Review-verdict` references the current head, and CI is green** — no separate confirmation, conversational or mechanical. **Root cause of the bug this fixes:** `pretooluse-merge-consent.sh`'s satisfied path did a bare `exit 0`. A PreToolUse hook that exits 0 with no JSON does **not** auto-approve — it only declines to block, so control falls through to the normal permission system and the raw MCP permission prompt still fires (the merge tool isn't allow-listed). So the gate only ever *added* a deny/ask net; it never *removed* the baseline prompt on the happy path. Net effect for the operator: a ready-to-merge PR was confirmed twice — once conversationally (the per-session merge-consent norm) and again by a redundant mechanical prompt the gate was supposed to have subsumed. **Fix:** ONLY the genuinely-satisfied merge path (a+b+c all true) now emits `{"hookSpecificOutput":{"permissionDecision":"allow", ...}}` (a new `grant` decision), which actually suppresses the prompt. Deny (unticked/red/negative/stale) and ask (non-derivable: no creds, Gitea down, no linked issue, no `## Done-when`, no verdict) are unchanged — the gate still fails closed, not open. Two paths deliberately do **not** auto-grant and keep the bare `exit 0` **passthrough** (normal permissioning → one prompt): non-merge `pull_request_write` methods (auto-grant is scoped to method=merge only), and the **docs/process-only exemption**. The exemption is a file-TYPE bypass, not the a+b+c "provably reviewed & ready" proof, so it must not *silently* self-merge — critically, its set includes `.claude/`/`.gitea/`/`.husky/` (the gate, CI workflows, and git hooks themselves), so a PR that weakens the gate still gets a human prompt (ersatztv#317 review nit). Verified by 8 pipe tests (satisfied→allow, docs-only→passthrough, unticked→deny, stale→deny, red-CI→deny, no-verdict→ask, no-creds→ask, non-merge→passthrough). **Process consequence:** the state-derived gate *is* the consent on the satisfied path — do **not** also ask conversationally to merge a PR whose gate auto-grants. A separate human confirmation is still warranted only when the gate **asks** (state not derivable). This supersedes the "always confirm merge consent in-conversation per session" phrasing in the kickoff HARD CONSTRAINTS (updated in the same PR).