Skip to content

Commit d6a8da5

Browse files
docs(agents): require disposition reply before resolving review threads (#3693) (#3740)
* docs(agents): require disposition reply before resolving review threads (#3693) Teach the disposition-reply convention across every agent/skill surface that calls or references resolveReviewThread, ahead of #3732's resolved_without_disposition convergence gate going live. Without this, the gate would block ~every new PR since no doc currently instructs a disposition reply before resolving. Convention: before any resolveReviewThread call, post a reply carrying Disposition: fixed | refuted | superseded | follow-up Evidence: <commit sha + test name> / <file:line + why> / <superseding head sha + seam> / <issue #N + why non-blocking> A thread must never be resolved with zero reply — the resolved-to-clear anti-pattern the #3647 incident shipped 6 live P1 defects through. - .claude/reference/review-convergence.md: new canonical "Disposition-reply convention" section — single source of truth every other doc links to, avoiding the five-file drift this doc already documents as this repo's recurring control-plane failure mode. - .claude/commands/pr-respond.md: step 4/4.5 require the disposition reply before the resolveReviewThread mutation call. - .claude/agents/pr-responder.md: steps 4/5 and the Principles section require the same before resolving. - .claude/commands/pr-ready.md, .claude/agents/ops.md, CLAUDE.md: verification-side references updated to cite the canonical convention. Does not touch scripts/ci/check-pr-review-convergence or its fixtures/ tests (PR #3732, already reviewed) — docs/skills only. * fix(docs): describe resolved_without_disposition gate as upcoming, not live Addresses review feedback on PR #3740 (chatgpt-codex-connector, P2): scripts/ci/check-pr-review-convergence still only blocks on pending reviewers, stale human reviews, and unresolved threads — it never reads comments.totalCount or emits resolved_without_disposition. The previous wording in all 6 docs implied that gate already mechanically enforces the disposition-reply convention (and review-convergence.md referenced a nonexistent "item 4 in The contract"), which would let /pr-ready and ops get exit 0 on a PR whose threads were resolved with zero reply and incorrectly believe it's blocked. Reworded every surface to state plainly: the resolved_without_disposition detection is proposed in #3732, which is deliberately held back for a dogfood-advisory-first rollout (this convention lands and is followed first, so the mechanical gate doesn't retroactively block PRs already in flight when it goes live). Until #3732 merges, the convention is process discipline verified by the agent/reviewer doing the work, not yet proven by the script's exit code. Disposition: fixed Evidence: this commit — reworded review-convergence.md, pr-respond.md, pr-responder.md, pr-ready.md, ops.md, CLAUDE.md to describe the gate as upcoming (#3732, held back) rather than live; removed the dangling "item 4 in The contract" cross-reference. * fix(docs): align disposition-format examples with the canonical spec Addresses review feedback on PR #3740 (factory-droid, 4x P2): 1. pr-respond.md step 4 examples used ';' as the evidence separator and inline '/' between Disposition and Evidence, drifting from the canonical `Evidence: <commit sha + test name>` ('+' joins compound evidence; '/' only separates the four alternative evidence shapes) spec in review-convergence.md. An agent copy-pasting the example would emit non-canonical replies. 2. CLAUDE.md, ops.md, pr-ready.md still said "accepted-with-follow-up" in the parenthetical disposition list, while the canonical convention (and every other list in this same PR) uses "follow-up". Two names for one concept in docs that link to each other. 3. pr-responder.md's Principles section "Reply with evidence" example was never migrated to the Disposition:/Evidence: format the step list two sections above now requires — an agent following the Principles bullet for a CI title fix would emit a free-form reply that satisfies neither the canonical format nor any future content-quality check. Disposition: fixed Evidence: this commit — pr-respond.md examples now use '+' within evidence and separate Disposition:/Evidence: lines; CLAUDE.md/ops.md/ pr-ready.md now say "follow-up" consistently; pr-responder.md's Principles bullet now demonstrates the canonical format for both a fix and a refute. Not fixed here (out of scope, flagged for follow-up): the reviewer also noted scripts/ci/check-pr-review-convergence:32,365 still says "accepted-with-follow-up" in its own BLOCK-line wording. That script is PR #3732's territory (already reviewed, deliberately not touched by this docs-only PR) — the drift there should be fixed when #3732 lands.
1 parent 0530d2f commit d6a8da5

6 files changed

Lines changed: 142 additions & 18 deletions

File tree

.claude/agents/ops.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ review code — that's the reviewers' job. You gate trusted change.
2020
- If CI fails, route to a fixer — don't debug yourself.
2121
- After parser merges, ratchet the corpus with `just cpan-corpus-ratchet`.
2222
- **Check both label AND draft state.** `merge-ready` label and `isDraft: false` are independent — a PR needs both. Use `/pr-ready` to exit draft if missed.
23-
- **Never merge — and never enable/retain auto-merge — while any requested review is still active or any substantive review conversation thread remains unresolved.** Threads must be resolved for a reason (fixed/refuted/superseded/accepted-with-follow-up), not performatively. `reviewDecision` alone doesn't prove either condition (it says nothing about thread resolution, and a review can predate the current push) — run the canonical review-convergence check (`scripts/ci/check-pr-review-convergence <number>`, see [.claude/reference/review-convergence.md](../reference/review-convergence.md)) in `/ops-merge-batch` step 2 before merging. Do not reproduce or modify its query locally.
23+
- **Never merge — and never enable/retain auto-merge — while any requested review is still active or any substantive review conversation thread remains unresolved.** Threads must be resolved for a reason (fixed/refuted/superseded/follow-up), each backed by a machine-readable `Disposition:`/`Evidence:` reply posted BEFORE resolution — never performatively. See the canonical convention in [.claude/reference/review-convergence.md § Disposition-reply convention](../reference/review-convergence.md#disposition-reply-convention-before-calling-resolvereviewthread) — the resolved-to-clear pattern that shipped 6 live P1 defects through #3647. **Not yet mechanically enforced:** the `resolved_without_disposition` gate proposed in #3732 (which would flag any resolved thread with no reply) is deliberately held back for a dogfood-advisory-first rollout, so `check-pr-review-convergence` does not yet detect this — verify it yourself before merging, don't rely on the script's exit code for it. `reviewDecision` alone doesn't prove either condition (it says nothing about thread resolution, and a review can predate the current push) — run the canonical review-convergence check (`scripts/ci/check-pr-review-convergence <number>`, see [.claude/reference/review-convergence.md](../reference/review-convergence.md)) in `/ops-merge-batch` step 2 before merging. Do not reproduce or modify its query locally.
2424
- **PR titles must end with `(#NNN)`.** validate-title CI check enforces this. If a PR fails on title, fix the title, don't skip the check.
2525
- **Don't rebase unless conflicts exist.** Unnecessary rebases trigger CI cascades on parallel PRs.
2626
- When main gets a CI fix, use `gh pr update-branch` on queued PRs, not `gh run rerun` (stale context).

.claude/agents/pr-responder.md

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,23 @@ For each bot comment / CI failure / review conversation:
4141
1. **Classify** — fix / refute / supersede / follow-up
4242
2. **Fix it on the branch** (or gather the refute/supersede/follow-up evidence) — checkout, edit, commit, push. For **follow-up**, don't just note it — create or identify the tracked issue first; a follow-up with no issue number is deferred work that silently disappears once the thread closes.
4343
3. **Prove it** — re-run the relevant check/test
44-
4. **Reply with evidence** — state what you fixed, with commit hash or reasoning; for follow-up, cite the issue number
45-
5. **Resolve the thread** — for the real reason (fixed/refuted/superseded/accepted-with-follow-up), never performatively
44+
4. **Reply with a machine-readable disposition** — BEFORE resolving,
45+
post a reply on the thread carrying the canonical format (see
46+
[.claude/reference/review-convergence.md § Disposition-reply
47+
convention](../reference/review-convergence.md#disposition-reply-convention-before-calling-resolvereviewthread)):
48+
```
49+
Disposition: fixed | refuted | superseded | follow-up
50+
Evidence: <commit sha + test name> / <file:line + why> / <superseding head sha + seam> / <issue #N + why non-blocking>
51+
```
52+
5. **Resolve the thread** — only after step 4's disposition reply exists.
53+
**A thread must never be resolved with zero reply** — that's the
54+
resolved-to-clear anti-pattern the #3647 incident shipped through (15
55+
threads `resolveReviewThread`'d with no reply, 6 live P1 defects merged
56+
on main). Required now as **process discipline**: the mechanical
57+
`resolved_without_disposition` detection is proposed in #3732 (held
58+
back for a dogfood-advisory-first rollout, so it doesn't retroactively
59+
block PRs already in flight) and does not yet block in
60+
`check-pr-review-convergence`.
4661
6. **Verify review convergence** before treating the PR as ready — run the
4762
canonical review-convergence check (see
4863
[.claude/reference/review-convergence.md](../reference/review-convergence.md)):
@@ -53,8 +68,16 @@ For each bot comment / CI failure / review conversation:
5368

5469
- **Fix everything, argue nothing you can't back with evidence.** If CI says title is wrong, fix the title. If clippy warns, fix the warning. If a test fails, fix the code. If a comment is wrong, refute it with evidence rather than silently ignoring it.
5570
- **Verify after fixing**`cargo test -p <crate>` after each commit.
56-
- **Reply with evidence** — "Fixed: updated PR title to include (#NNN). CI should re-run."
57-
- **Resolve conversations for a reason** — fixed/refuted/superseded/accepted-with-follow-up, not performatively. Never resolve a thread just to clear it.
71+
- **Reply with the canonical disposition** — every thread reply carries `Disposition:` and `Evidence:` per the convention, e.g. `"Disposition: fixed\nEvidence: <commit-sha> + test <name>"` for a title fix (`(#NNN)` added, CI re-run confirms); `"Disposition: refuted\nEvidence: <file:line>: <reasoning>"` for a refute.
72+
- **Resolve conversations for a reason, never performatively.** Post the
73+
`Disposition:`/`Evidence:` reply (see
74+
[.claude/reference/review-convergence.md](../reference/review-convergence.md#disposition-reply-convention-before-calling-resolvereviewthread))
75+
BEFORE calling `resolveReviewThread`. Never resolve a thread just to
76+
clear it — zero-reply resolution is the resolved-to-clear anti-pattern
77+
#3647 shipped 6 live P1s through. The `resolved_without_disposition`
78+
gate that will mechanically block on this (#3732) is deliberately held
79+
back for a dogfood-advisory-first rollout — follow the convention now
80+
regardless of whether the script enforces it yet.
5881
- **Never enable or retain auto-merge while any requested review is still active or any substantive thread is unresolved** — main mechanically requires conversation resolution before merge; verify reviewer completion before signaling readiness.
5982
- **Don't add improvements.** Fix what's broken, nothing more. Extra changes confuse the deep reviewer.
6083

.claude/commands/pr-ready.md

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,21 @@ scripts/ci/check-pr-review-convergence $NUMBER
5555
Do not reproduce or modify its query locally.
5656

5757
**Never enable or retain auto-merge, and never mark a PR ready for merge
58-
pickup, while the check above exits non-zero.** Resolve threads for a reason
59-
(fixed/refuted/superseded/accepted-with-follow-up), not performatively — main
60-
mechanically requires conversation resolution before merge. If the script
61-
exits non-zero, **STOP** and report which reviewer or thread is still
62-
pending (its `BLOCK` lines name them) instead of proceeding.
58+
pickup, while the check above exits non-zero.** Threads must be resolved
59+
for a reason (fixed/refuted/superseded/follow-up), each with
60+
a machine-readable disposition reply posted BEFORE resolution — see the
61+
canonical convention in
62+
[.claude/reference/review-convergence.md § Disposition-reply
63+
convention](../reference/review-convergence.md#disposition-reply-convention-before-calling-resolvereviewthread).
64+
Never performatively — main mechanically requires conversation resolution
65+
before merge. **Note:** the `resolved_without_disposition` detection
66+
(flagging a resolved thread with no reply) is proposed in #3732 and is
67+
**not yet live** in `check-pr-review-convergence` — it's held back for a
68+
dogfood-advisory-first rollout. Until it lands, treat the disposition
69+
requirement as process discipline you verify yourself, not something the
70+
script's exit code proves. If the script exits non-zero, **STOP** and
71+
report which reviewer or thread is still pending (its `BLOCK` lines name
72+
them) instead of proceeding.
6373

6474
### 4. Mark ready and signal merge-readiness
6575

.claude/commands/pr-respond.md

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,24 +38,50 @@ been classified `Fix`):
3838
3. Commit: `fix(review): address <reviewer> feedback — <what>`
3939

4040
### 4. Reply to comments
41+
42+
**Before resolving any thread**, reply with a machine-readable disposition —
43+
see the canonical convention in
44+
[.claude/reference/review-convergence.md § Disposition-reply
45+
convention](../reference/review-convergence.md#disposition-reply-convention-before-calling-resolvereviewthread):
46+
47+
```
48+
Disposition: fixed | refuted | superseded | follow-up
49+
Evidence: <commit sha + test name> / <file:line + why> / <superseding head sha + seam> / <issue #N + why non-blocking>
50+
```
51+
4152
```bash
4253
gh api repos/:owner/:repo/pulls/$ARGUMENTS/comments/<comment-id>/replies \
43-
-f body="Fixed in <commit-hash>. <brief explanation>"
54+
-f body="Disposition: fixed
55+
Evidence: <commit-hash> + test <name>"
4456
```
4557

4658
Or for general review comments:
4759
```bash
4860
gh pr comment $ARGUMENTS --body "Addressed review feedback:
49-
- <comment 1>: fixed in <hash>
50-
- <comment 2>: <explanation>
61+
- <comment 1>: Disposition: fixed
62+
Evidence: <hash> + test <name>
63+
- <comment 2>: Disposition: refuted
64+
Evidence: <file:line>: <reasoning>
5165
"
5266
```
5367

5468
### 4.5 Resolve the conversation thread
5569

56-
After replying with evidence, resolve the GitHub review thread — only once it
57-
has a real disposition (fixed/refuted/superseded/accepted-with-follow-up), not
58-
performatively:
70+
**A thread must never be resolved with zero reply** — that's the
71+
resolved-to-clear anti-pattern the #3647 incident shipped through (a
72+
responder silently `resolveReviewThread`'d 15 threads with no reply and no
73+
evidence; the PR merged with 6 live P1 defects). Follow this now as
74+
process discipline: `check-pr-review-convergence` does **not yet**
75+
mechanically detect a missing disposition reply — that detection
76+
(`resolved_without_disposition`) is proposed in #3732, held back for a
77+
dogfood-advisory-first rollout so it doesn't retroactively block PRs
78+
already in flight. A resolved thread with zero reply currently passes the
79+
script silently. Once #3732 lands, a resolved thread whose
80+
`comments.totalCount <= 1` (no reply beyond the original comment) will be
81+
`BLOCK`ing.
82+
83+
Only after step 4's disposition reply has been posted, resolve the GitHub
84+
review thread:
5985

6086
```bash
6187
gh api graphql -f query='mutation { resolveReviewThread(input: {threadId: "<thread-id>"}) { thread { isResolved } } }'

.claude/reference/review-convergence.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,3 +129,60 @@ outside of tests.
129129
If you are adding a new surface that needs this check, call the script —
130130
do not write a new GraphQL query for it, even if it looks like "just this
131131
one field."
132+
133+
## Disposition-reply convention (before calling `resolveReviewThread`)
134+
135+
**This is the canonical statement of the convention — every other doc
136+
that mentions resolving a review thread links here instead of restating
137+
it, to avoid the same five-file drift described in "Why this exists"
138+
above.**
139+
140+
Before **any** `resolveReviewThread` GraphQL call, the agent MUST first
141+
post a reply comment on that thread carrying a machine-readable
142+
disposition:
143+
144+
```
145+
Disposition: fixed | refuted | superseded | follow-up
146+
Evidence: <commit sha + test name> / <file:line + why> / <superseding head sha + seam> / <issue #N + why non-blocking>
147+
```
148+
149+
- `fixed` — the commit SHA that fixed it, plus the test name that proves it.
150+
- `refuted` — the `file:line` and the invariant/reasoning showing the
151+
concern doesn't apply.
152+
- `superseded` — the head SHA of the change that overtook this thread and
153+
the seam it replaces.
154+
- `follow-up` — the tracked issue number and why it's non-blocking here.
155+
Never write "will follow up" without a real issue number — untracked
156+
follow-up work silently disappears once the thread closes.
157+
158+
**A thread must never be resolved with zero reply.** That is the
159+
resolved-to-clear anti-pattern the #3647 incident shipped through: a
160+
responder silently `resolveReviewThread`'d 15 threads with no reply and no
161+
evidence, and the PR merged with 6 live P1 defects because nothing forced
162+
a reason to exist. Sequence is always **reply, then resolve** — never
163+
resolve first and explain later, never resolve without replying at all.
164+
165+
**Mechanical enforcement status (as of this writing): NOT YET LIVE.**
166+
`scripts/ci/check-pr-review-convergence` currently blocks only on pending
167+
reviewers, stale human reviews, and unresolved threads (items 1-3 above)
168+
— it does not read `comments.totalCount` and does not emit
169+
`resolved_without_disposition`. That detection (flagging any resolved
170+
thread whose `comments.totalCount <= 1` — no reply posted beyond the
171+
original review comment — as `BLOCK`ing) is proposed in #3732, which is
172+
deliberately **held back** for a dogfood-advisory-first rollout: this
173+
convention lands and is followed by agents first, so the mechanical gate
174+
doesn't retroactively block PRs already in flight when it goes live. Until
175+
#3732 merges, a resolved thread with zero reply passes
176+
`check-pr-review-convergence` silently — follow this convention as
177+
**process discipline**, verified by the agent/reviewer doing the work, not
178+
yet by the script's exit code. Once #3732 lands, the script enforces it
179+
mechanically. Even then, the script can only verify a reply's *existence*,
180+
not its *content* — following the exact `Disposition:`/`Evidence:` format
181+
above is what makes the reply useful to a human reader and to any future
182+
content-quality check, not just sufficient to pass the mechanical gate.
183+
184+
Every agent/skill that calls or instructs `resolveReviewThread` follows
185+
this convention: `.claude/commands/pr-respond.md` step 4.5,
186+
`.claude/agents/pr-responder.md` step 5, and the verification-side
187+
references in `.claude/commands/pr-ready.md` step 3.5 and
188+
`.claude/agents/ops.md`.

CLAUDE.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,8 +123,16 @@ recipe: [CI_GATE_PLAYBOOK.md](docs/reference/CI_GATE_PLAYBOOK.md).
123123

124124
**Never enable or retain auto-merge while any requested review is still active or any
125125
substantive review conversation remains unresolved.** Resolve threads for a reason
126-
(fixed/refuted/superseded/accepted-with-follow-up), not performatively — main
127-
mechanically requires conversation resolution before merge.
126+
(fixed/refuted/superseded/follow-up), each backed by a machine-readable
127+
`Disposition:`/`Evidence:` reply posted BEFORE resolution — never performatively. Main
128+
mechanically requires conversation resolution before merge. The
129+
`resolved_without_disposition` gate — which will mechanically block any resolved
130+
thread with no reply, the resolved-to-clear pattern that shipped 6 live P1 defects
131+
through #3647 — is proposed in #3732 but **deliberately held back** for a
132+
dogfood-advisory-first rollout, so it doesn't retroactively block PRs already in
133+
flight; until it lands, follow the convention as process discipline, not yet
134+
mechanically enforced. Canonical convention: [review-convergence.md § Disposition-reply
135+
convention](.claude/reference/review-convergence.md#disposition-reply-convention-before-calling-resolvereviewthread).
128136

129137
## Quick reference
130138

0 commit comments

Comments
 (0)