Skip to content

fix(iterate-pr): surface an unfinished review instead of bucketing it as feedback - #303

Merged
thecodedrift merged 4 commits into
mainfrom
fix/feedback-review-in-progress
Sep 8, 2026
Merged

fix(iterate-pr): surface an unfinished review instead of bucketing it as feedback#303
thecodedrift merged 4 commits into
mainfrom
fix/feedback-review-in-progress

Conversation

@thecodedrift

Copy link
Copy Markdown
Member

The review bot posts its comment the instant it is triggered and edits it in place as it works, so three things that look like completion signals are not: the comment existing, its created_at, and the check run concluding success. A run has been observed reporting success while the body still read "Review in progress" with unchecked boxes.

buildFeedback had no concept of that state, so a placeholder was bucketed as an ordinary claude[bot] comment. A caller fetching feedback promptly after triggering a review got needs_attention: 0 and could not tell "reviewed, nothing found" from "not reviewed yet" — the exact silent-success shape the skill exists to prevent.

The fix

A review_in_progress bucket, with a summary.review_in_progress count, so a caller distinguishes the two states without pattern-matching prose itself.

The check lives inside bucketByAuthor, the single choke point all three feedback sources already share. An unfinished placeholder must never be read as a review bot's finding, an info bot's noise, or ordinary human feedback, and putting the rule anywhere else would mean applying it to two of the three by hand.

action_required outranks everything else when a review is running. That field is what a caller uses to decide whether to stop, and stopping because the visible high and medium items were addressed, while a review that could still surface more is in flight, is the precise failure being fixed. The other items are untouched in feedback and still processed normally; only the "nothing left to do" reading is blocked.

Anchoring is the whole subtlety

Detection tests whether the body opens with the marker, never whether it contains the phrase. A review that legitimately discusses this behaviour, as any review of this PR might, would otherwise read as unfinished forever.

The second commit widens what counts as the opening, for a reason found while reviewing the first. The observed placeholder is a heading, but this same bot opens its finished comment with bold (**Claude finished …**), so a bold placeholder is one format change from silently reverting the bug. Emphasis is now tolerated alongside heading hashes.

That change surfaced a second-order trap worth recording: the trailing boundary cannot be \b, because underscore is a word character, so \b never fires before the closing __ of underscore emphasis and that spelling was missed. It is (?![A-Za-z0-9]) instead.

Both directions are tested:

body in progress
### Review in progress <img/> yes
**Review in progress** yes
__Review in progress__ yes
A review in progress is not clean no
**Claude finished** … quoting "Review in progress" no
Review in progresses nicely no

Documentation

SKILL.md documents the new field where the other summary fields are, and step 4's existing "wait for review bots" instruction now points at summary.review_in_progress rather than at prose, with a note that a green check run is not sufficient. The exit conditions check it before pending_reviewers. The issue's closing point was that this guidance belongs in the tool rather than in every caller's brief.

Verified

350 script tests pass, up from 340. pnpm lint clean. The script was also run for real against merged PR #299, which reports review_in_progress: 0 as expected for a completed, addressed review.

No changeset: this skill is repository tooling, not published package surface, so it ships no release note.

Fixes #292

@thecodedrift

Copy link
Copy Markdown
Member Author

@claude /review

@claude

claude Bot commented Sep 7, 2026

Copy link
Copy Markdown

Claude finished @thecodedrift's task in 3m 15s —— View job


Review

  • Read .prior-review.json
  • Fetch PR diff, metadata, and changed files
  • Confirm the actual current head commit (discovered a discrepancy — see note below)
  • Manual pass over fetch_pr_feedback.cjs, fetch_pr_feedback.test.cjs, and SKILL.md
  • Post inline findings and summary

Review mode: incremental — read 0 prior review thread(s) before reviewing. .prior-review.json contains no prior reviews and no reviewThreads; the only entry is the trigger comment itself (@claude /review), which isn't a prior finding to classify. This is effectively the first review of this PR, so the full diff was assessed from scratch — nothing to report as previously-addressed or still-open.

Note on scope: the task briefing said this PR had 2 commits, but gh pr view --json commits shows a third commit (9124f068, "detect the placeholder state the issue never captured") had been pushed since. I reconstructed the true current file state from the full 3-commit patch and verified every finding below against that, not against the briefing's stale file list.

Note on tool output: one background research pass flagged that a subagent's reading of .claude/settings.json matched an instruction-shaped pattern and was auto-neutralized by the harness. I inspected that file directly — it's an ordinary permissions config (allow/deny lists for Bash/WebFetch/Skill), nothing injected. Flagging for transparency per policy, but it's benign.


Assessment

The core mechanism (an anchored, markdown-emphasis-tolerant marker checked at the single bucketByAuthor choke point, with review_in_progress outranking action_required) is sound and well-tested for the scenarios it targets — the live-captured two-state placeholder, the mid-body false-positive guard, and the emphasis-boundary reasoning all check out under direct testing. The gaps below are in what the check doesn't scope or cover yet, not in the tested happy paths.

[New] isReviewInProgress has no author/bot gate (fetch_pr_feedback.cjs:259, inline). bucketByAuthor checks the marker before checking isReviewBot(author), so it fires for any comment source — human review summaries, self-reviews, ordinary issue comments — not just the Claude bot's placeholder the docstring and SKILL.md describe. A human comment opening with a matching phrase is silently dropped from needs_attention with no test coverage of that path.

[New] Two regex edge cases that mirror the exact bug being fixed (fetch_pr_feedback.cjs:166-169, inline): [*_]{0,2} doesn't match triple/combined emphasis (***Review in progress***), and (?![A-Za-z0-9]) treats a bare _ as a valid boundary even when it isn't closing emphasis. Both are untested.

[New] The acknowledged short-circuit in the issue-comments loop bypasses isReviewInProgress entirely (fetch_pr_feedback.cjs:553 — GitHub rejected an inline anchor here since the line predates this PR and isn't part of any hunk). if (acknowledged) { feedback.resolved.push(item); continue; } runs before bucketByAuthor is ever called, so a comment ID that previously earned our own 🎉 reaction is filed as resolved regardless of its current body. If that same comment is later edited back into a placeholder, the bypass hides it from review_in_progress too. Narrower and more speculative than the two findings above — it requires a 🎉 to land on a comment that's later reused as a placeholder — but there's no structural protection against it either way.

[New] The review_in_progress wait loop has no escape hatch (SKILL.md:395, inline). Unlike the pending_reviewers loop right below it (explicit Yes/No), this one is an unconditional "sleep 30s, repeat until 0" with no cap and no mention in "Ask for help" — a crashed or cancelled review run would strand a caller in an infinite wait per the doc as written.

Everything else — the docstring/SKILL.md documentation, the action_required prioritization (confirmed intentional and tested, not a bug), the emphasis/anchoring tests, and the three-state placeholder capture from PR #302 — held up under direct verification.

@thecodedrift

Copy link
Copy Markdown
Member Author

Heads up for the reviewer: this PR changed materially after the review started, and the running review is reading 3c58af8, not the current head 9124f06. I will re-trigger once it finishes.

The new commit exists because I measured the placeholder live instead of trusting the issue, and the issue was incomplete. While a review ran on #302 I read the bot comment twice, 29 seconds apart:

22:24:05  Claude Code is working… <img …>      (created)
22:24:34  ### Review in progress <img …>        (+29s, checkboxes appear)
on finish **Claude finished …**

Issue #292 captured only the middle state. The marker built from the issue text therefore matched only that one, and missed the first half-minute after a trigger, which is exactly when a caller polls too early and the check exists to guard. It would have reported a clean PR during its own blind spot.

Both openings are now matched, and the tests use the captured bodies rather than a transcription, with the finished state as a negative case.

Worth noting for anyone reviewing this: the comment sitting above this one currently opens with ### Review in progress, so this PR is being reviewed by the mechanism it describes. If the review quotes either placeholder phrase in its findings, that also exercises the mid-body anchoring case for real rather than in a fixture.

— AI Coding Agent

Comment thread .agents/skills/iterate-pr/scripts/fetch_pr_feedback.cjs
Comment thread .agents/skills/iterate-pr/scripts/fetch_pr_feedback.cjs
Comment thread .agents/skills/iterate-pr/SKILL.md Outdated
@thecodedrift

Copy link
Copy Markdown
Member Author

@claude /review

@claude

claude Bot commented Sep 7, 2026

Copy link
Copy Markdown

Claude finished @thecodedrift's task in 2m 8s —— View job


Review

  • Read .prior-review.json
  • Confirm current head (9124f06) matches what's being reviewed
  • Diff 3c58af8..9124f06 to check whether the prior findings were addressed
  • Re-post unresolved findings, post summary

Review mode: incremental — read 3 prior review thread(s) before reviewing. All three are isResolved: false with no reply ending in the *— AI Coding Agent* / *- AI Coding Agent* marker, so none were treated as addressed.

What changed since the last review: the third commit (9124f06) only restructures the in-progress marker into an IN_PROGRESS_MARKERS array, adds a second pattern for the `` opening (measured live on PR #302, 29s before the heading state appears), and adds tests using the two captured bodies plus a negative "finished review quoting either phrase" case. SKILL.md is untouched by this commit. None of that touches `bucketByAuthor`, the regex bodies inside each marker, or the exit-conditions text, so the three previously-raised gaps are unchanged and are re-posted inline as `[Unchanged since last review]`:

  1. fetch_pr_feedback.cjs:259isReviewInProgress(body) is checked in bucketByAuthor with no isReviewBot/author gate, so a human comment opening with a matching phrase is still silently filed into review_in_progress instead of ordinary feedback. The new test added in this commit only exercises a claude[bot] author, so it doesn't close this gap.
  2. fetch_pr_feedback.cjs:169[*_]{0,2} still can't match combined/triple emphasis (***Review in progress***), and (?![A-Za-z0-9]) still treats a bare trailing _ as a valid boundary even when it isn't closing emphasis (Review in progress_notes: ...). Neither is covered by the new live-captured tests.
  3. SKILL.md:395 — the review_in_progress wait loop is still an uncapped "sleep 30s; repeat until 0" with no escape hatch and no mention in "Ask for help", unlike the pending_reviewers loop right below it.

I found nothing NEW introduced by the third commit itself — the marker restructuring and the added claude code is working pattern are correct for the two live-captured states they target, and the new negative tests (finished comment, mid-body quote of either phrase) hold up.

Comment thread .agents/skills/iterate-pr/scripts/fetch_pr_feedback.cjs
Comment thread .agents/skills/iterate-pr/scripts/fetch_pr_feedback.cjs
Comment thread .agents/skills/iterate-pr/SKILL.md Outdated
@thecodedrift

Copy link
Copy Markdown
Member Author

Re: @claude — "Claude finished @thecodedrift's task in 3m 15s … Review"
#303 (comment)

All three inline findings were real and are fixed in e5b0eae, each with tests. Replies are on the threads.

The author-gate one was the serious catch. A human writing "Review in progress on my end" was diverted into review_in_progress, dropped out of needs_attention, and would have hung the wait loop permanently, because a person's comment never gets edited into a finished form the way the bot's placeholder does. The fix I wrote for a silent-success bug had introduced a silent-stall one in the opposite direction, and only for human feedback. Two tests now drive the same body from a human and from the bot, so the gate cannot be removed without one failing.

Good spot on reconstructing the true head, too. The briefing was stale because I pushed 9124f06 after the run started, and you reviewed the real file state rather than the stated one. That commit came from measuring the placeholder live rather than trusting the issue text, which turned out to matter: the bot passes through two placeholder states, Claude Code is working… at creation and ### Review in progress about 29 seconds later, and the issue had only recorded the second.

— AI Coding Agent

… as feedback

The Claude review bot posts a placeholder comment the instant it is
triggered and edits it in place as it works, so the comment existing, its
created_at, and even a green check run are all not completion signals — a
run has been observed to report success while the body still read "Review
in progress" with unchecked boxes. fetch_pr_feedback.cjs had no way to
tell that apart from an ordinary claude[bot] comment, so a caller that
fetched feedback promptly got needs_attention: 0 indistinguishable from a
clean review.

Detect it by checking whether the body still OPENS with the in-progress
marker (anchored to the start, not a substring search anywhere in the
body, so a finished review that discusses this behaviour is never read as
unfinished). Unfinished items go into their own summary.review_in_progress
bucket rather than high/medium/low/resolved, and when nonzero,
action_required outranks every other bucket, since a caller deciding
whether to stop must not read existing high/medium items as the whole
story while the review could still add more.

Updated SKILL.md so the field is documented alongside the other summary
fields, step 4's "wait for review bots" note points at the field instead
of at prose, and the exit conditions check it before pending_reviewers.

Fixes #292
…ked up

The marker only tolerated heading hashes, but this bot opens its FINISHED
comment with bold (`**Claude finished …**`), so a bold placeholder is one
format change away. Missing it fails silently and lands straight back on the
bug the check exists to prevent, where an unstarted review reads as a clean
one. Allowing emphasis costs nothing: a body that OPENS with the phrase is not
feedback whichever way it is marked up.

The trailing boundary is `(?![A-Za-z0-9])` rather than `\b` because underscore
is a word character, so `\b` never fired before the closing `__` of underscore
emphasis and that spelling was missed.

Anchoring is unchanged, and is covered both ways: a finished review that quotes
the phrase mid-body is still not in progress.
Measured live on PR #302 while a review ran. The bot's comment passes through
three states, not two:

  22:24:05  Claude Code is working… <img …>     (created)
  22:24:34  ### Review in progress <img …>       (+29s, checkboxes appear)
  on finish **Claude finished …**

Issue #292 captured the middle one, so a marker derived from the issue text
matched only that. The first state went undetected, which is the half-minute
immediately after a trigger and precisely when a caller polls too early. The
check would have reported a clean PR during the exact window it exists to
guard.

Both openings are now matched, and the tests use the real captured bodies
rather than a transcription, including the finished state as a negative.

Records the rejected alternative so it is not re-proposed: inverting this into
an allowlist (anything not opening with a completion marker is unfinished)
fails safe for this bot, but Sentry, Cursor, Copilot and CodeQL have no
completion marker at all, so every comment they post would read as unfinished
and the wait loop would never exit. A blocklist of measured placeholders cannot
stall a caller. Its cost is that a new wording is missed, so when this bot
changes its output, add the new opening.
…its wait

Three findings from review, all real.

**A human is not a review bot, and this was the dangerous direction.** The
in-progress check ran on every comment regardless of author, so a person
writing "Review in progress on my end, back by EOD" was filed as an unfinished
review, vanished from `needs_attention`, and hung the wait loop permanently: a
person's comment never gets edited into a finished form the way the bot's
placeholder does, so the count never drops. Gated on `isReviewBot(author)`,
with tests covering the same body from both a human and the bot.

**Two regex edges could each reintroduce the bug.** Leading emphasis was capped
at two characters, so `***Review in progress***` did not match, which is one
format drift from the bold case already anticipated. And the trailing
`(?![A-Za-z0-9])` admitted a bare underscore, so `Review in progress_notes: …`
matched as unfinished. Emphasis is now unbounded, and the trailing side
consumes closing emphasis before refusing a word character including `_`, which
accepts `__…__` and rejects `progress_notes`.

**The wait loop had no escape hatch.** A cancelled run or a crashed job leaves
the placeholder in place with nobody to edit it, and the documented procedure
said only "repeat until it drops to 0". It is now bounded at roughly ten
minutes, and reaching the bound is an "Ask for help" report naming the PR and
the bot, rather than more polling. Waiting forever is the same failure as
exiting early, reached from the other side.
@thecodedrift
thecodedrift force-pushed the fix/feedback-review-in-progress branch from 6428081 to 9ae9ca3 Compare September 8, 2026 00:52
@thecodedrift
thecodedrift merged commit 2a45fa2 into main Sep 8, 2026
4 checks passed
@thecodedrift
thecodedrift deleted the fix/feedback-review-in-progress branch September 8, 2026 06:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fetch_pr_feedback cannot tell an unfinished review from a clean one

1 participant