---
key: release.done-when-merge-consent
title: 2026-07-12 — Merge-consent derived from state via a `## Done-when` issue checklist (#303 H6)
status: active
since: '2026-07-12'
supersedes: none
superseded-by: none
rule: A PR may merge only when its linked issue's `## Done-when` checklist is fully ticked and the PR's CI is green, enforced by a PreToolUse hook on the Gitea merge tool (deny/allow/ask) plus a pre-push backstop for direct pushes to main.
signals: 'Done-when checklist, merge consent, state-derived gate · paths: `.claude/settings.json`, `.husky/pre-push` · issues: #303 (H6)'
mechanics: '`pretooluse-merge-consent.sh`; `prepush-donewhen.sh`; CLAUDE.md → Task Completion Protocol'
---

**An issue's `## Done-when` checklist (in the issue body) is the machine-readable source of truth for whether
its PR may merge; consent is *derived*, not asserted.** Rationale: DONE/OPEN status used to live in
append-only prose that lags live Gitea state (the queue-drift #303 fixes) — so the completion gate moves out
of memory and into a checklist two hooks read. Convention: the issue body carries a `## Done-when` section
(always an "adversarial review passed" box, plus per-issue criteria); a merge is allowed only when the PR's CI
is green **and** every box on the linked issue (`fixes #N`) is ticked.

Enforcement (both fail *safe*, never a silent pass):
- `pretooluse-merge-consent.sh` — Claude PreToolUse on `mcp__gitea__pull_request_write` merge: **deny** on an
  unticked box or non-green CI; **allow** when both satisfied; **ask** (human prompt) when state isn't
  derivable (no linked issue, no `## Done-when`, no creds, Gitea unreachable). Docs-only PRs exempt.
- `.husky/pre-push` → `prepush-donewhen.sh` — backstop for a direct `git push origin main`; fail-*open* (a
  git hook has no "ask"), blocks only on a positively-proven unticked box.

Both authenticate to Gitea from env only (`ETV_GITEA_BASICAUTH` / `ETV_GITEA_TOKEN`, `ETV_GITEA_URL`) — no
creds committed; without them the gate degrades to today's manual confirmation. Rollout is non-breaking: until
issues adopt `## Done-when`, the merge hook simply *asks* rather than auto-allowing. See CLAUDE.md → Task
Completion Protocol. (H6 lives with H1/H2/H8 in `.claude/settings.json`; H7 worktree-owner guard is its
sibling Wave-2 hook.)
