[CAT-UNVERIFIED tag](2) A rewritten reply still gets checked for proof - #416
Conversation
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_48ffe756-9080-46fc-990d-666452c46c97) |
|
Queued — the merge queue status continues in this comment ↓. |
|
@Mergifyio queue |
Merge Queue Status
This pull request spent 4 minutes 43 seconds in the queue, with no time running CI. ReasonThe pull request conflicts with the base branch The pull request was held in the queue, conflicting with 1 pull request ahead of it. The conflict became a base branch one after #421 merged. HintYou should update or rebase your pull request. If you want to requeue this pull request, you can post a Tick the box to put this pull request back in the merge queue (same as
|
|
Mergify repair stopped: GitHub reports merge conflict. The retry cap was reached for current head ae767ea. |
diu-stop and prove-it-ship-gate both returned on stop_hook_active before any check ran. For diu-stop the reason was real -- trimming words reveals more words to trim, and nine consecutive blocks on one 150-word message were observed -- but returning early passed the whole message. The first block of a turn bought a free pass for whatever the rewrite said next, including a claim that was never checked. That is what happened in the session behind this change. The same message, sent as a retry, on 509b9cd and on this stack: before (509b9cd) exit=0 (silent) after exit=2 `UNVERIFIED:` is no longer an escape hatch -- it reads as ordinary prose and the claim beside it is judged on its own... A diu-stop retry now skips only the word count. prove-it-ship-gate has no word count, so its bypass was a pure free pass and is gone. These checks cannot loop the way the word count did: a well-formed tag always passes and every block message names it, so there is always a move that ends the turn. test_naming_the_blocker_ends_the_turn and test_naming_the_blocker_ends_the_turn_on_retry pin that. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VKsvxJk65w6q7KnPSRYvNg
ae767ea to
e34b32f
Compare
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_b7fff640-a2d7-4494-9548-5a240a1841f8) |
Summary
Before Claude sends a reply, small checker scripts read it. One checks length. Others check that every claim comes with proof. If a check fails, Claude must rewrite.
The problem: when Claude rewrote, both checkers stepped aside completely. So the second draft could say anything, even a brand-new claim with no proof.
Why they stepped aside: the length check once blocked one reply nine times in a row. Letting every rewrite through ended that loop.
The fix: on a rewrite, only the length check steps aside. The proof checks run every time.
Before and After
We took the real rewrite that exposed this and sent it as a second try, first on the old code (
509b9cd), then on this change.exit=0means the reply went through.exit=2means it was blocked.Review Claim
Approve this: on a rewrite, only the length check is skipped. The proof checks run every time.
Review Lane
behavior
Review Unit
engine-runtime
Safety Invariant
Claude always has a way to finish. The length check still steps aside on a rewrite, so the nine-block loop cannot come back. The proof checks always accept a note that says what Claude could not check and why.
test_naming_the_blocker_ends_the_turnandtest_naming_the_blocker_ends_the_turn_on_retrytest this.One known gap: this PR's checker names only the first unproven sentence, so a reply with two of them gets blocked twice. PR #433, stacked on this one, makes one block list every sentence.
Slice Rationale
This is part 2 of a stack. Part 1 adds the note that lets Claude say "I could not check this, and here is why." That note is the way out of a block, so this change needs part 1 first.
Non-goals
Test Plan
Test Plan
python3 engine/skills/make-pr/scripts/preflight.py --base <this PR's base>bash scripts/run_all_tests.sh-- exit 0, 45 suites OK, 0 failed, 1674 teststest_naming_the_blocker_ends_the_turn(diu-stop) andtest_naming_the_blocker_ends_the_turn_on_retry(prove-it-ship-gate) -- a tagged claim passes with and withoutstop_hook_activetest_retry_stops_checking_the_word_count-- the word count still does not fire on a retryRevert Plan
Revert Plan
git revert <sha>🤖 Generated with Claude Code
Note
Medium Risk
Changes agent-facing stop-hook behavior for every rewrite after a block; mis-tuning could reintroduce word-count loops or over-block legitimate retries, though tests lock in the split behavior.
Overview
Stop-hook rewrites are no longer a blanket bypass for proof checks. When
stop_hook_activeis set after a first block,claude_stop_checkandprove-it-ship-gateused to exit immediately; rewrites could introduce new unverified claims (e.g. bareUNVERIFIED:) without re-checking.Only the diu word-count gate is relaxed on retry, because that check could loop (nine blocks on one long message). On retry,
over_limitis false even when overWORD_LIMIT; unverified-claim, malformed{{CAT-UNVERIFIED}}, and ship-gate logic still run. The intended way to finish is a well-formed{{CAT-UNVERIFIED: … -- cannot verify: <reason>}}(or real evidence), documented in the module docstring.Tests replace “passes on
stop_hook_active” with cases that retries still block bad claims, still skip word count only, and pass once the blocker is named in the tag.Reviewed by Cursor Bugbot for commit e34b32f. Bugbot is set up for automated code reviews on this repo. Configure here.