Skip to content

Recalibrate Claude PR review severity to cut false positives - #4406

Closed
keppo-bot[bot] wants to merge 2 commits into
mainfrom
pr-review-severity-calibration
Closed

Recalibrate Claude PR review severity to cut false positives#4406
keppo-bot[bot] wants to merge 2 commits into
mainfrom
pr-review-severity-calibration

Conversation

@keppo-bot

@keppo-bot keppo-bot Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Cuts the false-positive rate of the Dyadbot PR reviewer by fixing its severity calibration. Prompt-only β€” no pipeline or workflow changes, so codex-pr-review.yml is unaffected.

Why

I audited every dyad-assistant inline comment on the last 25 PRs by wwwillchen/keppo-bot (#4343–#4400): 229 comments across 83 review runs. Each was re-judged against the actual code at the commit it was posted against.

The reviewer is not hallucinating β€” 196 of 229 findings are factually true, only 2 are outright false. The problem is that it has no way to say "minor":

posted β†’ deserved HIGH MEDIUM LOW not worth posting
HIGH (17) 12 3 1 1
MEDIUM (212) 2 50 135 25

71% of inline comments did not deserve to be inline. Independent signals agree:

  • 117 of 176 resolved threads were closed with no reply at all
  • 41 MEDIUMs were left open on PRs that merged anyway
  • only 3 of 83 runs ever concluded "ready to merge"; 66 said NOT SURE
  • 2 genuine HIGHs were buried at MEDIUM, because everything is MEDIUM

The three causes this PR fixes

1. There was no severity rubric. The only guidance was Use the same review standard as Dyad's multi-persona PR review, which resolves to code-health-reviewer.md:21: "Sloppy code that hurts maintainability is a MEDIUM severity issue, not LOW. We care about code health." The prompt never defined LOW at all, so the model had no criterion to demote anything.

Replaced with an impact-anchored rubric (HIGH breaks something, MEDIUM degrades a demonstrable behavior, LOW is everything else), an explicit always-LOW list β€” duplication, dead code retained during a migration, naming/comments, missing test coverage, defensive guards for inputs no caller passes, telemetry shape, LLM prompt wording β€” and a calibration note that 0 HIGH / 0–2 MEDIUM is the expected result, and finding nothing above LOW is a good outcome.

2. There was no admission gate. The old line 3 licensed "maintainability problems that can cause future defects" β€” an invitation to report things that are not defects yet. All 60 speculative and all 48 scope-creep findings in the corpus were over-posted.

Now: the first sentence of body must name the concrete trigger (input/state β†’ wrong outcome); if you can't write it without "could/may/might/risks", it's LOW. Scope-creep remedies ("retire the setting", "migrate the other specs") are LOW by rule. Base-branch behavior is out of scope. Constraint 4 changes from a disclaimer ("say so plainly") into an admission gate ("uncertainty is a reason to demote").

3. Three personas and a 7-bullet checklist acted as quotas. "Code Health Expert" maps almost exactly onto the style/duplication/dead-code categories; "UX Wizard" produced the cursor-pointer notes. The Dyad-specific bullets read as an audit checklist to run against every PR.

Collapsed to one senior-engineer voice, and the Dyad bullets are now gated triggers β€” "apply a check ONLY if the diff touches the thing it names".

Safety

Each rule was checked against the 14 confirmed merge-blockers in the corpus β€” every one names a concrete trigger and a concrete wrong outcome, so all 14 survive. The rubric also recovers the two HIGHs that shipped at MEDIUM.

Deliberately not done here: LOW findings stay out of the findings JSON. validate-review.mjs:203 throws when findings exist with no Issues Summary table, and the posting step is continue-on-error β€” a LOW-only run would silently drop all of its inline output.

Testing

  • render-template.mjs renders the new template successfully with the same three variables (CONTEXT_PATH, OUTPUT_MD_PATH, OUTPUT_FINDINGS_PATH); no new {{VAR}} introduced.
  • The summary/findings contract validate-review.mjs parses is unchanged: verdict line, recommendation line, ### Issues Summary table shape, and findings JSON schema are all byte-identical to before. The example separator row still matches the row filter added in Fix PR review validation for hyphenated issue pathsΒ #4405.
  • Real effect can only be measured in production. Suggested check after a week: share of inline comments that get a substantive reply or a code change, and inline comments per run (baseline 2.8).

Follow-ups not in this PR

πŸ€– Generated with Claude Code

Review in cubic

An audit of the last 25 PRs by wwwillchen/keppo-bot (#4343-#4400) found
229 inline comments from dyad-assistant across 83 review runs. Re-judging
each one against the code at the reviewed commit: 196 were factually
true, but only 67 deserved to be inline comments. 135 MEDIUMs should have
been LOW and 25 should not have been posted at all -- 71% miscalibrated.
Corroborating signals: 117 of 176 resolved threads got no reply at all,
41 MEDIUMs were left open on PRs that merged anyway, and only 3 of 83
runs ever concluded "ready to merge".

Three causes in this prompt, and the fixes:

1. No severity rubric. The only guidance was a pointer to the
   multi-persona standard, whose code-health reviewer says verbatim
   "Sloppy code that hurts maintainability is a MEDIUM severity issue,
   not LOW". Replaced with an impact-anchored rubric plus an explicit
   always-LOW list (duplication, dead code, naming, comments, missing
   tests, defensive guards, telemetry shape, prompt wording), and a
   calibration note that 0 HIGH / 0-2 MEDIUM is the expected result.

2. No admission gate. "Maintainability problems that can cause future
   defects" invited hazards that are not defects yet; all 60 speculative
   and all 48 scope-creep findings were over-posted. Findings now must
   name a concrete trigger in the first sentence of `body`, scope-creep
   remedies are LOW by rule, and base-branch behavior is out of scope.
   Constraint 4 becomes an admission gate rather than a disclaimer.

3. Three personas and a 7-bullet checklist acted as quotas. Collapsed to
   one senior-engineer voice; the Dyad-specific bullets are now gated
   triggers that apply only when the diff touches what they name.

LOW stays out of the findings JSON: validate-review.mjs throws when
findings exist with no Issues Summary table, and the posting step is
continue-on-error, so a LOW-only run would silently drop all inline
output.

Verified the template still renders with the same three variables and
that the summary/findings contract validate-review.mjs parses is
unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@keppo-bot
keppo-bot Bot requested a review from a team August 28, 2026 05:20

@chatgpt-codex-connector chatgpt-codex-connector 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.

πŸ’‘ Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4cf5bdf57f

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 πŸ‘.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread .github/prompts/claude-pr-review.txt Outdated

Before you assign HIGH or MEDIUM, write the trigger to yourself: the specific input or state, and the specific wrong outcome. If you cannot write it without the words "could", "may", "might", "if someone later", or "risks", the finding is LOW.

Findings about LLM system-prompt or instruction text are LOW by default. They may be MEDIUM only when the text over-promises a capability: it names a tool the model will not have, claims a provider is connected when the code gates it off, or instructs an action the runtime forbids. Under-specification, wording, ordering, duplication between prompt constants, and "this could confuse the model" are always LOW.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep harmful prompt instructions eligible for escalation

When a PR changes a local-agent system prompt to direct an available state-modifying tool to delete or overwrite user files, this rule still classifies the resulting data-loss defect as LOW because the instruction does not over-promise an unavailable capability. That finding is then excluded from the findings JSON and cannot affect the ready recommendation; only speculative wording concerns should be categorically demoted, while executable instructions need normal impact-based severity.

Useful? React with πŸ‘Β / πŸ‘Ž.

@dyad-assistant

Copy link
Copy Markdown
Contributor

πŸ” Dyadbot Code Review Summary

Verdict: πŸ€” NOT SURE - Potential issues
Recommendation: ready

Prompt-only change to .github/prompts/claude-pr-review.txt. I verified the machine-readable contract the pipeline depends on is intact: no new {{VAR}} tokens for render-template.mjs; the **Verdict:** / **Recommendation:** lines, the ### Issues Summary header and 3-cell table shape, and the findings JSON schema that validate-review.mjs parses are all unchanged; and the new no-issues line :white_check_mark: No issues found. still contains the MULTI_AGENT_NO_ISSUES substring that scripts/pr-status-labeler.js:15 matches on, so PR labeling does not regress. The diff is complete (not truncated).

The two notes below are about the new policy text weakening the security/correctness net, not about the pipeline breaking. Neither blocks merge.

Issues Summary

Severity File Issue
🟑 MEDIUM .github/prompts/claude-pr-review.txt:34 Always-LOW rule can demote unvalidated IPC input
🟑 MEDIUM .github/prompts/claude-pr-review.txt:15 PR-body intent claim can suppress correctness findings
🟒 Low Priority Notes (5 items)
  • Stale no-issues guard in the validator - validate-review.mjs:195 guards on the literal :white_check_mark: No significant issues found., which matched neither the old nor the new no-issues line, so that consistency check stays dead. Pre-existing, but this diff edits the exact sentence it was meant to pair with. (.github/prompts/claude-pr-review.txt)
  • Footer now collides with the swarm skill - _Generated by Dyadbot code review_ is the same wording as .claude/skills/swarm-pr-review/SKILL.md:250, so summaries from the two pipelines are no longer distinguishable by footer text. Nothing parses the footer today, so this is cosmetic. (.github/prompts/claude-pr-review.txt)
  • Inconsistent gating verbs in the Dyad checks - Some bullets read "Adds or changes" (IPC handler, local agent tool, E2E tests) while others read only "Adds" (renderer data fetching, UI primitive, user-facing flow, database schema change). The preamble's "if the diff touches the thing it names" resolves the ambiguity, but the asymmetry invites a narrow reading on schema modifications. (.github/prompts/claude-pr-review.txt)
  • The prompt exempts itself - "LLM system-prompt and instruction text" is on the always-LOW list, which means future edits to this very file can only ever produce LOW findings from this reviewer. Probably intended, worth being aware of. (.github/prompts/claude-pr-review.txt)
  • Base-branch claim is accurate - "The checkout available to you is the BASE branch, not the PR head" matches claude-pr-review.yml:45 checking out github.sha under pull_request_target. Noting it as verified, not as a problem. (.github/prompts/claude-pr-review.txt)

Generated by Dyadbot persona-based code review

@github-actions github-actions Bot added the needs-human:review-issue ai agent flagged an issue that requires human review label Aug 28, 2026
Codex review on #4406 caught that the intent bullet was written as an
unconditional suppression rule: an author who describes a security
regression as intentional would force the reviewer to demote it, and
since LOW never becomes an inline comment and the recommendation stays
`ready`, an author-controlled description could hide a merge blocker.

Split intent into two rules. Stated intent settles design disagreements
-- disagreeing with a deliberate product decision stays LOW, which is
what the 22 intent-misread findings in the audit needed. Intent never
lowers the severity of a demonstrable defect: a crash, data loss, a
corrupted write, a security or IPC boundary hole, a failing test suite,
or a broken feature is reported at the severity its impact earns, with a
note that the description calls it intentional.

Reviewing the fix surfaced the same failure mode twice more in the rules
I added, both of which would have cost confirmed must-catch findings:

- "remedy is work beyond this PR's stated scope" hangs a suppression rule
  on an author-controlled phrase, and reads as covering [4381-1], where
  deleting AutoApproveSwitch stranded a Playwright helper in a file the
  diff never touched -- 121 call sites across 60 specs would time out.
  Now split explicitly: breakage is a defect wherever its remedy lives;
  only optional follow-up work is LOW.
- The always-LOW list could contradict the HIGH definition, e.g. for
  [4381-8] where the remedy is "migrate the specs" but the outcome is a
  failing suite. Added a precedence rule -- impact always wins over
  category -- rather than trying to word each bullet defensively.

Re-checked all 14 confirmed merge-blockers from the audit against the
revised rules; all 14 still surface at HIGH. Re-checked the intent
demotions that motivated the original bullet (#4398's fail-closed auth
policy, #4394's picker restructure); both remain LOW, since neither
crashes, breaks a feature, or fails a test.

Template still renders with the same three variables.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector 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.

πŸ’‘ Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a987e32eaa

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 πŸ‘.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Severity is decided by user-visible impact, not by code quality.

- HIGH - the diff as written breaks something. You can name the input or state that triggers it and the wrong outcome someone sees: a crash, data loss, a corrupted write, a security or IPC boundary hole, a test suite that will fail, a feature that will not work.
- MEDIUM - the diff as written degrades a real behavior you can demonstrate, but the feature still works: a wrong error message, a missing error state on a path users hit, a diagnostic that logs the wrong value, an edge case that produces a confusing result.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Classify incorrect diagnostics consistently

When a diff changes a diagnostic to log the wrong value, this line classifies the defect as MEDIUM, while line 37 says telemetry and logging are always LOW. Those mutually exclusive instructions make the same change eligible for an inline finding or mandatory suppression depending on which rule the reviewer follows, undermining the intended severity recalibration; choose one severity for incorrect diagnostic content.

Useful? React with πŸ‘Β / πŸ‘Ž.

@dyad-assistant

Copy link
Copy Markdown
Contributor

πŸ” Dyadbot Code Review Summary

Verdict: πŸ€” NOT SURE - Potential issues
Recommendation: ready

Prompt-only change to .github/prompts/claude-pr-review.txt. The diff is complete (diffTruncated: false), and I verified the surrounding machinery it has to stay compatible with: .github/workflows/claude-pr-review.yml, scripts/issue-agent/render-template.mjs, scripts/pr-review/validate-review-summary.mjs, and scripts/pr-review/validate-review.mjs.

The machine-checked contract survives intact. No new {{VAR}} is introduced, so render-template.mjs still renders with the three variables the workflow supplies. The verdict line, recommendation line, ### Issues Summary table shape, and findings JSON schema are unchanged, so both validators still parse the output. I also grepped the repo for consumers of the two renamed strings (No issues found by persona-based review. and the persona-based footer) β€” nothing outside the prompt itself matches them, so those renames are safe.

The issues below are about the new severity rubric's interaction with rules elsewhere in the same file. None of them break the pipeline, so none block merge. Because the deliverable is instruction text, the real effect is behavioral and can only be confirmed on live runs β€” I am flagging specific internal conflicts I can point at in the diff, not predicting an outcome.

Issues Summary

Severity File Issue
🟑 MEDIUM .github/prompts/claude-pr-review.txt:35 Always-LOW input-guard rule can demote missing IPC input validation
🟑 MEDIUM .github/prompts/claude-pr-review.txt:19 Cross-file defect guidance conflicts with the changed-file path requirement
🟑 MEDIUM .github/prompts/claude-pr-review.txt:55 Dyad UI checks narrowed from changed to newly added code
🟒 Low Priority Notes (4 items)
  • Dead no-issues guard left unaligned - Line 135 changes the no-issues string to :white_check_mark: No issues found., but validate-review.mjs:195 guards on ":white_check_mark: No significant issues found.". That guard was already dead before this PR, and it stays dead after; since this diff edits that exact line, aligning the two would be cheap. (.github/prompts/claude-pr-review.txt)
  • Prompt-only PRs become largely self-exempt - The always-LOW entry for "LLM system-prompt and instruction text" (line 38) plus the dedicated rule at line 46 means future prompt-tuning PRs, including this one, get almost nothing above LOW. The description makes clear this is deliberate, and the over-promising carve-out is a sensible floor; noting it so the trade-off is on the record. (.github/prompts/claude-pr-review.txt)
  • Verdict line stays uninformative under the new calibration - With 0 HIGH expected on a typical PR, both YES - Ready to merge and NOT SURE - Potential issues remain valid whenever MEDIUMs exist, so the verdict still carries little signal. The description already lists this as a follow-up. (.github/prompts/claude-pr-review.txt)
  • Dead-schema clause dropped from the schema check - Line 57 keeps only the Drizzle migration condition; the base text also asked about "dead schema/infrastructure that is not used by the PR". That is now covered by the always-LOW dead-code entry, so the drop looks intentional. (.github/prompts/claude-pr-review.txt)

Generated by Dyadbot persona-based code review

@github-actions

Copy link
Copy Markdown
Contributor

🎭 Playwright Test Results

❌ Some tests failed

OS Passed Failed Flaky Skipped
🍎 macOS 279 1 1 12

Summary: 279 passed, 1 failed, 1 flaky, 12 skipped

Failed Tests

🍎 macOS

  • local_agent_auto.spec.ts > local-agent - auto model
    • Error: expect(string).toMatchSnapshot(expected) failed

πŸ“‹ Re-run Failing Tests (macOS)

Copy and paste to re-run all failing spec files locally:

npm run e2e \
  e2e-tests/local_agent_auto.spec.ts

⚠️ Flaky Tests

🍎 macOS

  • select_component.spec.ts > select component next.js (passed after 1 retry)

πŸ“Š View full report

@wwwillchen wwwillchen closed this Aug 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-human:review-issue ai agent flagged an issue that requires human review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant