---
key: release.review-verdict-gate
title: '2026-07-12 — Review-verdict merge-gate: latest commit must be reviewed (#303 H10)'
status: active
since: '2026-07-12'
supersedes: none
superseded-by: none
rule: 'A PR may not merge until a `Review-verdict: <MERGEABLE|APPROVED|BLOCKED|NOT-MERGEABLE> @ <head-sha>` comment references the PR''s current head sha (short-sha prefix match, line-start marker only, negative wins over positive on the same head); folds into the H6 merge-consent hook as condition (c).'
signals: 'review-verdict, head-sha match, stale-review prevention · paths: `.claude/settings.json` · issues: #303 (H10), #242'
mechanics: '`pretooluse-merge-consent.sh`; CLAUDE.md → Task Completion Protocol (H10 convention)'
---

**A PR may not merge until a `Review-verdict:` comment on it references the PR's CURRENT head sha** —
so the *latest* commit is proven-reviewed, not a stale earlier diff. This mechanizes the ersatztv#242
lesson ("re-review the fix commit, not just the initial PR diff": a review of an earlier revision does
not license merging a head that carries un-reviewed follow-up commits). It folds into the existing H6
`pretooluse-merge-consent.sh` as condition (c), reusing its PR fetch, docs-only exemption, and
Gitea-auth-from-env (no second hook → no detection drift, per the #303 methodology review).

Convention: after reviewing a PR (or its latest fix commit), post a PR **comment** (issue-style, not a
Gitea formal-review body — the gate reads `issues/{pr}/comments`) whose line **starts with** the marker:
`Review-verdict: <MERGEABLE|APPROVED|BLOCKED|NOT-MERGEABLE> @ <head-sha>` (short ≥7-char or full sha).
The gate counts a line as a verdict only when the marker is at line-start (after optional indent) —
a comment that merely *quotes* the template mid-sentence (an instruction "please post: Review-verdict:
MERGEABLE @ …", or the gate's own suggestion text echoed back) does **not** self-approve the merge
(adversarial re-review false-open, folded pre-merge). It then classifies each verdict line by the sha
in its `@ <sha>` field, matched to the head by **git short-sha prefix semantics** (head *begins with*
the token, token ≥7 chars) — NOT a loose substring test, so an older sha that merely contains the head
prefix, or the head prefix appearing in an unrelated URL on the line, does not count:
- a MERGEABLE/APPROVED/LGTM verdict whose `@ <sha>` is the current head → **allow**;
- a **negative** verdict (BLOCKED/NOT-MERGEABLE) *on the head* → **deny**, and it *wins over* a positive
  one on the same head (a later BLOCKED retracts an earlier MERGEABLE; to retract, re-review head and
  post BLOCKED @ head). Staleness is **symmetric on purpose**: a negative for an *older* commit is stale
  exactly like a positive for an older commit, and does NOT override a fresh head-positive — otherwise a
  pre-fix `BLOCKED @ oldsha` would block forever even after the fix changes the sha and earns a fresh
  `MERGEABLE @ head` (the normal flow). So a genuine block must reference head, per the convention;
- verdict comment(s) exist but reference only *older* commits → **deny** — the stale-review case #242
  targets;
- a `Review-verdict:` marker with **no `@ <sha>`** at all → **ask** (a lazy/quoted marker; not
  mislabelled as stale);
- no `Review-verdict:` comment at all → **ask** (graceful adoption, mirrors H6's "no Done-when →
  ask": surface, don't hard-block a PR that hasn't adopted the convention yet);
- comments unfetchable / head sha unresolvable → **ask**.

Scope: the Claude PreToolUse gate on the Gitea merge tool only. A direct `git push origin main` has no
PR comments to check, so the `.husky/pre-push` backstop is not extended for H10 (the merge tool is the
real merge path; docs-only PRs remain exempt via H6's file-set exemption). Rationale, as with the whole
Wave-1/2/3 hook set: make the process rule a derivation/hook, not prose to remember (#303).
