Skip to content

Stop announcing a reviewer's agreement as a rebuttal#40

Merged
kristofferR merged 2 commits into
mainfrom
fix/reply-verdict-and-skip-block
Jul 26, 2026
Merged

Stop announcing a reviewer's agreement as a rebuttal#40
kristofferR merged 2 commits into
mainfrom
fix/reply-verdict-and-skip-block

Conversation

@kristofferR

@kristofferR kristofferR commented Jul 26, 2026

Copy link
Copy Markdown
Owner

Problem

crq surfaces a bot's reply to a declined finding so a rebuttal is never buried. But it announced every reply that was not an explicit withdrawal as

Reviewer contests your reply — re-address or reply again

including plain agreement. An agent told to re-address a rebuttal that did not exist looped on the artifact instead of moving on. From a real session:

"Same thread again — but crq is labeling CodeRabbit's agreement as "reviewer contests your reply". Let me verify the real state rather than loop on an artifact."

dialect.IsReviewFindingRetained already existed for exactly this discrimination, and was called only from a golden test — never in production.

Change

The retained classifier now decides the claim:

  • a reply that really retains the finding → reported as a contest;
  • anything else → reported as a reply to read and confirm the decline stands.

Visibility is deliberately unchanged. Both keep the same major floor, because a possible rebuttal that gets buried is the worse failure. Only the assertion about what the reply means was wrong.

Also: a regression test for a fleet-wide property

CodeRabbit's "Review skipped" refusal ships with the rate-limit marker embedded, and reading it as a timed block was catastrophic rather than merely wrong: the block is account-wide, nothing about waiting clears "this PR has 119 files", and every re-fire re-read the same notice and re-blocked. One oversized PR stalled review for every repository.

The classifier discriminates them today, but the cost of a regression lands in the queue rather than in classification, so this asserts the consequence: after observing the refusal no account block exists, and an unrelated PR still fires. It also asserts the refusal was actually processed, so the test cannot pass vacuously.

Verified, not changed

Two items this PR was scoped to fix turned out to be already fixed, and are recorded here so they are not re-investigated:

Tests

gofmt -l ., go vet ./..., go test ./... clean. Local CodeRabbit preflight clean after addressing its findings.

Summary by CodeRabbit

  • Bug Fixes

    • Improved review-thread rebuttal messages so they distinguish between retained findings and ambiguous replies.
    • Added clearer guidance when a reviewer responds after a decline, prompting confirmation that the decline still stands.
    • Fixed skipped-review refusals so they no longer block unrelated review activity across the account.
  • Tests

    • Added coverage for rebuttal classification and account-level blocking behavior.

Ref #42.

crq surfaces a bot's reply to a declined finding so a rebuttal is never buried.
But it announced EVERY reply that was not an explicit withdrawal as
"Reviewer contests your reply — re-address or reply again", including plain
agreement — and an agent told to re-address a rebuttal that did not exist looped
on the artifact instead of moving on.

`dialect.IsReviewFindingRetained` already exists for exactly this and was called
only from a golden test, never in production. It is now what decides the claim:

- a reply that really retains the finding → reported as a contest;
- anything else → reported as a reply to read and confirm.

Visibility is deliberately unchanged. Both keep the same major floor, because a
possible rebuttal that gets buried is the worse failure — only the assertion
about what the reply *means* was wrong.

Also adds the regression test for a property that has no test today: a
"Review skipped" refusal must never block the account. That notice ships with the
rate-limit marker embedded, and reading it as a timed block was catastrophic
rather than merely wrong — the block is account-WIDE, nothing about waiting
clears "this PR has 119 files", and every re-fire re-read the notice and
re-blocked, so one oversized PR stalled review for every repository. The
classifier discriminates them; the cost of a regression lands in the queue, so
the test asserts the consequence: no block is recorded, and an unrelated PR still
fires.

While verifying, two items from the plan turned out to be already fixed and
needed no change: the refusal misclassification (PR #35, with a golden row whose
comment describes this failure mode) and the co-review wait's evidence floor,
which already anchors on when the head appeared rather than when crq noticed it.
@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR differentiates rebuttal finding titles based on retained replies and adds replay coverage ensuring skipped review refusals do not block unrelated pull requests at the account level.

Changes

Rebuttal title classification

Layer / File(s) Summary
Conditional rebuttal titles and assertions
internal/crq/feedback.go, internal/crq/feedback_test.go
threadRebuttal uses retained-reply detection to select the finding title, while tests distinguish retained contests from ambiguous replies that require the caller to read and confirm the decline.

Replay refusal isolation

Layer / File(s) Summary
Skipped refusal replay regression
internal/crq/replay_test.go
A replay test verifies that a skipped review refusal leaves account blocking unset and does not prevent an unrelated pull request from firing.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

Poem

A rabbit reads the rebuttal bright,
“Contests” when findings stay in sight.
Ambiguous words say, “Read it through,”
No skipped review can block the queue.
New tests hop onward—fresh and true.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and broadly matches the main change to reply classification in declined review threads.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/reply-verdict-and-skip-block

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@kristofferR

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Nice work!

Reviewed commit: 43f4c1550b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@kristofferR

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@kristofferR

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/crq/feedback.go`:
- Around line 1179-1182: Move the user-facing title templates currently assigned
in threadRebuttal to an allowed lower-layer package, and have threadRebuttal
call that package for the title based on IsReviewFindingRetained(last.Body).
Keep threadRebuttal limited to classification and finding assembly, with no bot
wording or duplicated decision logic in internal/crq.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: de83eb9a-dff3-41b6-99bd-d46ef8c58dcd

📥 Commits

Reviewing files that changed from the base of the PR and between 6b772a0 and 43f4c15.

📒 Files selected for processing (3)
  • internal/crq/feedback.go
  • internal/crq/feedback_test.go
  • internal/crq/replay_test.go
📜 Review details
🧰 Additional context used
📓 Path-based instructions (2)
internal/crq/**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

internal/crq/**/*.go: Keep internal/crq orchestration-only; it owns service flow and wires packages but must not contain bot wording or duplicate engine decision logic.
Use the observe → decide → apply flow: observation is built once, engine decisions remain pure, and crq/service.go is the only effects executor for CAS writes and PostIssueComment.
DryRun must short-circuit apply by reporting without writing state or posting comments.
Keep crq loop exit codes frozen: 0 for converged/skipped, 10 for findings, and 2 for timeout.

Files:

  • internal/crq/replay_test.go
  • internal/crq/feedback.go
  • internal/crq/feedback_test.go
internal/{dialect,engine,state,crq,gh}/**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

Maintain the dependency direction dialect ← engine ← crq, state ← crq, and gh ← {state, crq}; do not introduce dependency cycles.

Files:

  • internal/crq/replay_test.go
  • internal/crq/feedback.go
  • internal/crq/feedback_test.go
🔇 Additional comments (2)
internal/crq/replay_test.go (1)

709-773: LGTM!

internal/crq/feedback_test.go (1)

1934-1935: LGTM!

Also applies to: 1952-1963

Comment thread internal/crq/feedback.go Outdated
Both reviewers flagged the title templates sitting in internal/crq, and they are
right — I had declined the same point at preflight on the grounds that dialect
owns bot wording rather than crq's own output. That reasoning does not hold:
dialect already derives finding titles (TitleOf) and reason strings, and the
package rule is that internal/crq is orchestration only.

So the classification and the words describing it now travel together.
`dialect.ClassifyDeclineReply` returns a `ReplyVerdict` — withdrawn, retained or
unclear — and the verdict carries its own `TitlePrefix`. crq reads the verdict and
assembles the finding; it no longer decides how a verdict reads.

This also removes the shape that caused the bug: two independent boolean calls,
where consulting only the first meant every non-withdrawal reply was announced as
a contest. A single verdict cannot be half-classified.

The golden rows now assert the verdict for every captured reply and that only a
stated rebuttal claims to contest, so the wording cannot drift from the
classification it describes.
@kristofferR

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Swish!

Reviewed commit: ae9d982a5d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@kristofferR

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@kristofferR
kristofferR merged commit c88b700 into main Jul 26, 2026
2 checks passed
@kristofferR
kristofferR deleted the fix/reply-verdict-and-skip-block branch July 26, 2026 15:05
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.

1 participant