dev-lead: switch on the maintainer no-change path safely (unrecorded-reply gap, live proof of stamping, cutoff wiring) — follow-up to #2079 / PR #2084 #2161
Description
Context
PR #2084 "dev-lead: resolve a bot review thread on a maintainer 'no change needed' disposition" (issue #2079) added the no-change path but shipped it switched off. nochange_epoch_cutoff in scripts/dev-lead-fix-reviews.sh reads DEV_LEAD_NOCHANGE_EPOCH, and no workflow sets that variable, so no thread is resolved on a maintainer's no-change verdict yet. The complaint in #1743 is therefore not fixed.
This issue is the code work needed before the path can be switched on. It was re-scoped on 2026-10-10 to four criteria dev-lead can do in one PR; the maintainer-only steps are listed at the end and are not for dev-lead.
Already shown (was criterion 2): the recorder reaches the model. A ring0 pass stamped a real reply on PR #2135; see the 2026-10-10 comment below.
Acceptance criteria (dev-lead)
- Unstamped replies survive between passes. Today
stamp_pass_replieswrites ids it could not stamp to${RUNNER_TEMP:-/tmp}/dev-lead-unstamped-replies-<pr>. Each pass runs on a fresh hosted runner, so the next pass never reads that file.- Keep the pending ids somewhere a later pass can read, for example a marker comment on the PR like dev-lead's other state markers.
- The no-change path stays disabled on that PR until the pending list is empty.
- Test: a pass that fails to stamp reply X, followed by a second pass with a fresh temp directory, still knows about X.
- An unattributable reply does not discard the attributable ones, and is not forgotten.
- When the record holds
UNATTRIBUTED,cl_recorded_reply_idsreturns 1 andstamp_pass_repliessetsrecorded="", so replies whose ids were captured in that pass are neither stamped nor carried over. Stamp every id that was recorded, even when the pass as a whole is unattributable. - A reply whose id is unknown can never be stamped later. Persist that fact the same way as criterion 1, so the no-change path stays off for that PR until a human clears it.
- The recorder currently flags any argument that starts with
@or contains=@whose remainder is not a readable file (for example a comment body beginning with@coderabbitai). Narrow that so an ordinary comment is not treated as an unattributable reply. Keep the fail-closed behaviour for a real reply-creating call whose payload cannot be read. - Tests for each bullet. Commit
6adb065a(in PR feat: implement issue #2079 — dev-lead: resolve a bot review thread on a maintainer "no change needed" disposition (rebuild of #1799 on #2049's resolver) #2084) added none for the recorder's widened matching; add them.
- When the record holds
- Resolver gaps CodeRabbit raised on PR feat: implement issue #2079 — dev-lead: resolve a bot review thread on a maintainer "no change needed" disposition (rebuild of #1799 on #2049's resolver) #2084.
- In
resolve_deferred_bot_threadsthe no-change branch runs after deferral parsing and the post-marker gate, so a thread with no deferral never reaches it. Either move it ahead of those gates, or remove the unreachable branch and document thatresolve_addressed_bot_threadsis the only entry point. - A failed
resolveReviewThreadon the no-change path inresolve_addressed_bot_threadsonly logs a warning. Make it retryable the way the deferred resolver is.
- In
- Wire the cutoff, still off by default. Pass
DEV_LEAD_NOCHANGE_EPOCHfrom an Actions variable into the dev-lead job indev-lead-reusable.yml. Unset or unparseable must keep the path disabled, exactly as today. Do not set the variable.
Not for dev-lead (maintainer, after this merges)
- Set the variable only after the release containing this work is on every consumer's dev-lead channel, including
dev-lead/v139-stable. Record the value and the release here. - Decide whether a reply posted through an absolute-path
gh(which bypasses aPATHshim) is an accepted gap. - Close Harness cannot resolve a bot review thread whose correct response is 'no change needed' — false positives block merge permanently #1743 once one real bot thread has been resolved on a maintainer's no-change comment.
Not in scope
Changing which phrases count as a no-change verdict.
Activity
github-actions commentedon Oct 9, 2026
Thanks for the detailed follow-up and the clear ordered checklist. The unrecorded-reply gap in item 1 is the blocker for switching the path on, so one thing would help before work starts: can you confirm the exact
ghinvocation forms (RESTreplies,in_reply_to, and GraphQL via--inputor-F query=@file) you've seen the model use in real passes, so we can cover those in the shim and its tests?don-petry commentedon Oct 10, 2026
Evidence for acceptance criterion 2 (the recorder reaches the model), 2026-10-10.
dev-lead/v139-ring0moved tov139.52.0(71920a24), which contains PR #2084. On PR #2135, a dev-leadfix-reviewspass on that release (run 38029736552) posted a review-thread reply that now carries<!-- dev-lead:reply -->: #2135 (comment)Of the 5 replies dev-lead posted on that PR since 03:00Z, 1 was stamped by the harness, 4 already carried another dev-lead marker, and none is marker-less. That is one PR and one pass, so it shows the recorder works on the instructed reply path; it does not cover the other call paths listed in criterion 1.
Generated by Claude Code
don-petry commentedon Oct 11, 2026
Dev-Lead Implementation Plan
Issue: #2161 — dev-lead: switch on the maintainer no-change path safely (unrecorded-reply gap, live proof of stamping, cutoff wiring)
Scope
Make unstamped and unattributable pass replies durable across passes, narrow the recorder's
@matching, close the two resolver gaps CodeRabbit raised on #2084, and wireDEV_LEAD_NOCHANGE_EPOCHfrom an Actions variable. The path stays off by default.Implementation Checklist
scripts/lib/claim-landing.shgh apicalls can be reply-creating. Read payload files only from--inputand from-F/--field key=@file. A-f body=@coderabbitai …orgh pr comment --body "@coderabbitai …"is no longer flagged. An unreadable--input/-F @fileon a reply call still fails closed.cl_recorded_reply_ids: emit every captured id even when the record holdsUNATTRIBUTED, and keep returning 1 in that case. Newcl_record_unattributedpredicate.cl_pending_marker_body <ids>andcl_pending_marker_ids <body>. Marker:<!-- dev-lead:unstamped-replies ids=… -->.scripts/dev-lead-fix-reviews.shstamp_pass_replies: drop the$RUNNER_TEMPfile. Read the pending list from our own marker comment(s) on the PR (union), stamp recorded and pending ids, and carry anUNATTRIBUTEDentry forward until a human clears it. Rewrite the marker, or delete it when the list is empty. A non-empty list, or a marker that cannot be read, keeps the no-change path off. A reply that no longer exists, or a node that is not a review comment, drops out of the list.build_and_run: if no record file exists when the engine runs, the pass is marked unattributable.resolve_deferred_bot_threads: move the no-change branch ahead of the own-reply/deferral parse and the post-marker gate. Bot-origin and full-page checks still run first.resolve_addressed_bot_threads: a failedresolveReviewThreadon the no-change path, or a response that is notisResolved: true, now counts as a failure, and the function returns non-zero. All three callers (fix-reviews, fix-bot-comment, review-changes) then post theresolve-failedretry marker and withhold auto-merge, as they already do for the deferred resolver..github/workflows/dev-lead-reusable.yml:DEV_LEAD_NOCHANGE_EPOCH: ${{ vars.DEV_LEAD_NOCHANGE_EPOCH || '' }}in the dev-lead job env. Unset or unparseable keeps the path disabled. The variable is not set.Tests to Write
-f body=@coderabbitai …(raw field) is not flagged;gh pr comment --body "@coderabbitai …"is not flagged;-F body=@<missing>on a reply call fails closed;-F body=@filewhose file holds a reply mutation is recorded;--inputwith an unreadable path fails closed.cl_recorded_reply_idsemits the captured ids alongsideUNATTRIBUTED(rc 1);cl_record_unattributed.stamp_pass_replies: pass 1 fails to stamp X, then pass 2 with a fresh temp dir still knows X from the PR marker and retries it; the marker is deleted once the list is empty.stamp_pass_replies: a record holdingUNATTRIBUTEDplus Y stamps Y and persistsUNATTRIBUTED; a later clean pass still returns non-zero.resolve_deferred_bot_threads: a maintainer no-change verdict on a bot thread with no deferral resolves it (epoch set) on a no-commit pass, and does not resolve it with the epoch unset.resolve_addressed_bot_threads: a failed no-change resolve returns non-zero and posts aresolve-failedretry marker.DEV_LEAD_NOCHANGE_EPOCHis mapped fromvarsin the dev-lead job.Test Command
bats tests/dev-lead/unit/test_claim_landing.bats tests/dev-lead/unit/test_fix_reviews.bats(plusbash .dev-lead/scripts/dev-lead-lint.sh)coderabbitai commentedon Oct 11, 2026
🧩 Analysis chain
🏁 Script executed:
Length of output: 76848
🏁 Script executed:
Length of output: 40176
🏁 Script executed:
Length of output: 9225
@don-petry, thanks for the guidance. The plan covers the gaps in the current code. KeepDEV_LEAD_NOCHANGE_EPOCHunset.Add these safeguards to the implementation and tests:
PullRequestReviewComment. Keep the id on transport errors, GraphQL errors, or incomplete responses.isResolved: true. HTTP success alone is not sufficient. The deferred branch currently also discards the response.|| rc=1. On failure, set_REVIEW_INCOMPLETE=1, clear_AM_NEEDS_RESTORE, and post the retry marker. Do not post a success terminal first. Preserve the PR-widefix-reviewsretry intent forfix-bot-comment.replies, RESTin_reply_to, GraphQL through--inputand-F query=@file, and stdin input. Assert that the shim preserves arguments, stdin, output, and exit status. These tests do not establish which forms the model used in live passes.For a checkout of this repository, the lint command is
bash scripts/dev-lead-lint.sh. The.dev-lead/scripts/dev-lead-lint.shpath is the workflow runtime path.rather than runner-local files. UNATTRIBUTED persists until human clearance. Wire DEV_LEAD_NOCHANGE_EPOCH from Actions vars in .github/workflows/dev-lead-reusable.yml, but do not set the variable; the maintainer no-change path must remain disabled by default.
-->
✏️ Learnings added
💡 For code changes across multiple files, use
@coderabbitaiplan in an issue commentYou are interacting with an AI system.
don-petry commentedon Oct 11, 2026
Dev-Lead: Implementation Complete — PR #2239
The implementation for issue #2161 is durable: commits are pushed and a pull request is open. This record is backed by verifiable artifacts you can confirm in one click.
aa11039b79b6cbd64fc861998fd98c86c9826843main: 6 file(s), +722/-107 linesReview happens on the PR. Acceptance criteria and test results are evidenced by the PR's diff and CI checks — not asserted here. If a later stage fails, this claim is retracted in place.
feat: implement issue #2161 — dev-lead: switch on the maintainer no-c…
feat: implement issue #2161 — dev-lead: switch on the maintainer no-c…
fix(reviews): reject a no-change verdict when a bot finding was edite…
Merge origin/main into fix/issue-2261-bot-issue-comment-disposition