feat(loop): comment-fix — automated bounded fix attempts for unresolved PR review threads (issue #96, part 2)#159
Conversation
…ed PR review threads (issue #96, part 2) Adds pr-comment-fix.sh (issue #96 capability #2): detects open, bot-authored PRs with an unresolved, qualifying (owner or allowlisted-bot) review-comment thread, excluding feedback candidates, needs-human/claude-comment-fixing labeled PRs, and threads already addressed for their current state. Bounds retries to 2 attempts per thread (derived from the claude-comment-addressed marker itself, no new state file) before escalating to needs-human. Wires the new comment-fix reaction into the loop at the documented precedence (feedback > comment-fix > ci-fix > advance > resume): census surfaces comment_fix_prs=N, loop-tick.sh adds it as step 5/6 and emits action=comment-fix pr=N, loop-event.sh builds its driver prompt, and loop-daemon.sh gives comment-fix drivers their own transient unit naming (pr-loop-driver-commentfix-pr<N>). pr-ci-fix.sh now also excludes comment-fix candidates from its own detection (mirroring its existing feedback exclusion). Adds the commentFix.botAllowlist config surface to gates.json (empty by default — only the owner's threads qualify). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…t empty allowlist TESTS reviewer rejection on PR #159 (issue #96 part 2): the anti-livelock escalation scenario had no positive assertion that PR 17's budget-exhausted thread actually triggers `gh pr edit 17 --add-label needs-human` -- "PR 17 not emitted" alone can't distinguish correct escalation from a silent drop. Mirror pr-feedback.test.sh's gh-call-log convention: the fixture's fake bot-gh.sh now appends every invocation to a log file, and the test greps it directly (PR 17 labeled, PR 16 -- attempt 2, not yet exhausted -- is not). Also add a fixture2 scenario with commentFix.botAllowlist: [] (the shipped DEFAULT), previously never exercised since fixture1 hardcodes a non-empty allowlist everywhere: a bot-only commenter does not qualify, while an owner comment in the same run still does, proving the owner always qualifies independent of the allowlist. Test-only change; no production logic touched.
|
feat(loop): comment-fix — automated bounded fix attempts for unresolved PR review threads (issue #96, part 2) (not yet reviewed) |
25 similar comments
|
feat(loop): comment-fix — automated bounded fix attempts for unresolved PR review threads (issue #96, part 2) (not yet reviewed) |
|
feat(loop): comment-fix — automated bounded fix attempts for unresolved PR review threads (issue #96, part 2) (not yet reviewed) |
|
feat(loop): comment-fix — automated bounded fix attempts for unresolved PR review threads (issue #96, part 2) (not yet reviewed) |
|
feat(loop): comment-fix — automated bounded fix attempts for unresolved PR review threads (issue #96, part 2) (not yet reviewed) |
|
feat(loop): comment-fix — automated bounded fix attempts for unresolved PR review threads (issue #96, part 2) (not yet reviewed) |
|
feat(loop): comment-fix — automated bounded fix attempts for unresolved PR review threads (issue #96, part 2) (not yet reviewed) |
|
feat(loop): comment-fix — automated bounded fix attempts for unresolved PR review threads (issue #96, part 2) (not yet reviewed) |
|
feat(loop): comment-fix — automated bounded fix attempts for unresolved PR review threads (issue #96, part 2) (not yet reviewed) |
|
feat(loop): comment-fix — automated bounded fix attempts for unresolved PR review threads (issue #96, part 2) (not yet reviewed) |
|
feat(loop): comment-fix — automated bounded fix attempts for unresolved PR review threads (issue #96, part 2) (not yet reviewed) |
|
feat(loop): comment-fix — automated bounded fix attempts for unresolved PR review threads (issue #96, part 2) (not yet reviewed) |
|
feat(loop): comment-fix — automated bounded fix attempts for unresolved PR review threads (issue #96, part 2) (not yet reviewed) |
|
feat(loop): comment-fix — automated bounded fix attempts for unresolved PR review threads (issue #96, part 2) (not yet reviewed) |
|
feat(loop): comment-fix — automated bounded fix attempts for unresolved PR review threads (issue #96, part 2) (not yet reviewed) |
|
feat(loop): comment-fix — automated bounded fix attempts for unresolved PR review threads (issue #96, part 2) (not yet reviewed) |
|
feat(loop): comment-fix — automated bounded fix attempts for unresolved PR review threads (issue #96, part 2) (not yet reviewed) |
|
feat(loop): comment-fix — automated bounded fix attempts for unresolved PR review threads (issue #96, part 2) (not yet reviewed) |
|
feat(loop): comment-fix — automated bounded fix attempts for unresolved PR review threads (issue #96, part 2) (not yet reviewed) |
|
feat(loop): comment-fix — automated bounded fix attempts for unresolved PR review threads (issue #96, part 2) (not yet reviewed) |
|
feat(loop): comment-fix — automated bounded fix attempts for unresolved PR review threads (issue #96, part 2) (not yet reviewed) |
|
feat(loop): comment-fix — automated bounded fix attempts for unresolved PR review threads (issue #96, part 2) (not yet reviewed) |
|
feat(loop): comment-fix — automated bounded fix attempts for unresolved PR review threads (issue #96, part 2) (not yet reviewed) |
|
feat(loop): comment-fix — automated bounded fix attempts for unresolved PR review threads (issue #96, part 2) (not yet reviewed) |
|
feat(loop): comment-fix — automated bounded fix attempts for unresolved PR review threads (issue #96, part 2) (not yet reviewed) |
|
feat(loop): comment-fix — automated bounded fix attempts for unresolved PR review threads (issue #96, part 2) (not yet reviewed) |
|
feat(loop): comment-fix — automated bounded fix attempts for unresolved PR review threads (issue #96, part 2) (not yet reviewed) |
9 similar comments
|
feat(loop): comment-fix — automated bounded fix attempts for unresolved PR review threads (issue #96, part 2) (not yet reviewed) |
|
feat(loop): comment-fix — automated bounded fix attempts for unresolved PR review threads (issue #96, part 2) (not yet reviewed) |
|
feat(loop): comment-fix — automated bounded fix attempts for unresolved PR review threads (issue #96, part 2) (not yet reviewed) |
|
feat(loop): comment-fix — automated bounded fix attempts for unresolved PR review threads (issue #96, part 2) (not yet reviewed) |
|
feat(loop): comment-fix — automated bounded fix attempts for unresolved PR review threads (issue #96, part 2) (not yet reviewed) |
|
feat(loop): comment-fix — automated bounded fix attempts for unresolved PR review threads (issue #96, part 2) (not yet reviewed) |
|
feat(loop): comment-fix — automated bounded fix attempts for unresolved PR review threads (issue #96, part 2) (not yet reviewed) |
|
feat(loop): comment-fix — automated bounded fix attempts for unresolved PR review threads (issue #96, part 2) (not yet reviewed) |
|
feat(loop): comment-fix — automated bounded fix attempts for unresolved PR review threads (issue #96, part 2) (not yet reviewed) |
What this does
Part 2 of #96: gives the autonomous PR loop a fourth reaction to a
loop-authored, open PR — an unresolved, qualifying inline review-comment
thread now gets an automated fix attempt instead of sitting open forever
waiting on a human to notice.
pr-comment-fix.sh(new): detects open, bot-authored PRs with anUNRESOLVED review-comment thread (GraphQL
reviewThreads, since REST hasno "resolved" concept for these) that has at least one comment from the
repo owner or an allowlisted bot commenter, excluding: PRs that are ALSO a
pr-feedback.shcandidate (precedence); PRs labeledneeds-humanorclaude-comment-fixing(in-flight guard, mirroringpr-ci-fix.sh'sclaude-ci-fixing); and threads whose fix was already posted for thethread's CURRENT state (a
<!-- claude-comment-addressed:<thread_id>:<attempt> -->marker cursor, keyed to the thread id + attempt number, so a genuinely NEW
comment reopening the same thread still re-triggers).
directly from the marker's own embedded attempt number (no new state
file). A thread that reopens after 2 completed fix attempts is escalated
to
needs-humaninstead of retried again.loop-census.sh: surfaces the count ascomment_fix_prs=N, foldedinto the existing
cadence=FASTtrigger alongsidefeedback_prs/ci_fix_prs.loop-tick.sh: wirespr-comment-fix.shin as step 5/6 (betweenpr-feedback.shandpr-ci-fix.sh), parses its TSV, and emitsaction=comment-fix pr=Nat the documented precedence.loop-event.sh: builds the COMMENT-FIX driver prompt — labelclaude-comment-fixingfirst (in-flight guard), fix on the same branch(no new branch), push, resolve ONLY the threads actually addressed, then
post the
claude-comment-addressedmarker(s) with the real thread id(s)and attempt number(s). Never merges, never force-pushes.
loop-daemon.sh: gives comment-fix drivers their own transientsystemd unit shape (
pr-loop-driver-commentfix-pr<N>, distinct from bothpr-loop-driver-pr<N>for feedback andpr-loop-driver-cifix-pr<N>forci-fix) so drivers for the same PR number can never collide in naming or
reattach.
pr-ci-fix.sh: now ALSO excludes comment-fix candidates from its owndetection (mirroring its existing feedback exclusion), enforcing the
ci-fix < comment-fix precedence twice (belt and suspenders).
gates.json/self/gates.json: newcommentFix.botAllowlistconfigsurface (empty array = disabled, same empty-means-skip convention as
notify) — the repo owner's own review threads always qualify regardlessof this list; fill it in to let a specific bot reviewer's threads
auto-trigger comment-fix too.
.claude/commands/pr-loop.md/.claude/self/pr-loop-self.md: STEP 0tick contract updated to mention the new step and verdict.
Decision: verdict precedence
feedback > comment-fix > ci-fix > advance. Owner
CHANGES_REQUESTEDalways outranks everything else. An unresolved qualifying review-comment
thread outranks a CI fix (the reopened conversation is addressed before
chasing a possibly-unrelated CI failure) and a fresh advance dispatch. Merge
still requires a fresh owner approval regardless of any of this —
merge-ready.shis untouched.Bounded attempts
Comment-fix dispatches reuse the same per-issue attempt-budget counter
introduced in #95 (
loop-issue-attempts.json) for the overalldispatch-count ceiling — no new state file for that. The PER-THREAD retry
budget (2 attempts before escalating that specific thread) is a
separate, finer-grained bound, derived entirely from the marker comments
already posted to the PR (see
pr-comment-fix.sh's header doc).Out of scope
Part 3 of #96 (conflict-after-sibling-merge rebase) is not included
here — still open, tracked separately.
🤖 Generated with Claude Code