Repository navigation
fix(ci): work an earlier FAIL's notes when address-feedback finds its threads resolved - #12843
Conversation
…esolved A lanes App pull request the merge gate refuses verify:earlier-fail could not leave that state once its pr-verify fail-note threads were resolved: address-feedback found no thread, ended success with no push, and the dispatcher re-ran the same no-op round. - verify-cap.mjs returns the reader's earlierFails as earlierFailHeads on a verify:earlier-fail refusal, and no longer warns on it. - feedback-dispatch.mjs defers the no-unresolved-thread skip on a lanes App PR until the verifier read, and sends a model round naming the FAILed heads in the new verify-earlier-fail-heads input. - address-feedback-select (now with actions: read) reads those FAILs' notes from their verdict artifacts (earlier-fail-notes.mjs: the FAIL index, a run the gate counts, readVerdict and noteComments) as verify_notes work items; a read that fails escalates feedback-verdict-unreadable. - The review-thread tool lists the notes and records VALID or already-addressed for them; apply-patch names fixed notes in the Lane-Thread trailer; publish-feedback posts nothing for notes, ends a pushed note fix as a fix round, and escalates feedback-already-addressed (needs-session) when nothing was pushed and every note is addressed. The reader's refusal rules are unchanged. Closes #12809 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ueued The reply job's feedback-already-addressed stop is lane-stopped, which releases the head for the dispatcher, so the next trigger would spend another model turn to reach the same stop. The select job now skips awaiting-session before reading any artifact when that head's stop is queued for a session builder, as it already does for untaken threads. earlier-fail-notes.mjs passes over a run the merge gate would not count, as the gate's reader does, and fails closed only when no counted FAIL is left to read. The pull-request skill's mode table names the notes. Refs: #12809 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A verifier read can take two minutes, and the sweeps decide PR after PR in one 10-minute job, so an unconditional read on every lanes App PR with no unresolved thread could time the dispatcher out for every lane. That path now reads only when the threads the dispatcher already holds carry a pr-verify fail note from a review of another head (no extra call); other PRs keep the free skip. One pass spends at most EARLIER_FAIL_READS (3) such reads through a budget main shares across its sweeps; the rest skip unrecorded for the next pass. Refs: #12809 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 066ef298ea
ℹ️ 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".
address-feedback.mjs, which the merge gate's floor holds, loads the new earlier-fail-notes.mjs, so the floor must name it: test_merge_holds_sync failed for it in test-bash, test-python and check-skills (babysit-prs's engine.test.sh). The row goes in docs/conventions/pr-pipeline.yaml as an enforcement path beside verify-notes.mjs, scripts/sync-hold-floor.py regenerates its copies, and the floor's count and digest pins move by that one row. Refs: #12809 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…FAIL On a lanes App PR with no unresolved thread, a verifier read that failed or timed out returned no FAILed heads, the round skipped as having no thread, and no sweep sent the head again: sweepUnsent took only heads with an unresolved thread (Codex review on #12843). The same held for a head the per-pass read cap left. verifyReader now marks such a read readFailed. The no-thread path skips a failed read, or a cap read that could not count every FAIL, without recording it, and records under the dispatch record's earlier-fail-read key only a definitive "no earlier FAIL" answer, which the tick also saves. sweepUnsent sends a thread-less head that holds a fail note from another head and has no such answer yet. The EARLIER_FAIL_READS cap stays. Refs: #12809 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
claude-security-review reviewed Security review: PR #12843, incremental (
|
|
claude-review reviewed Code review (incremental, afb7ddc → abccf5d): PR #12843Scope. I read every hunk of all 3 files in this round's change:
For context I also read the head versions of What changed. The
What I checked:
Findings: none. Prior and settled points
Scope notes
Spend: an answered head with no unresolved thread can be read again by the verifier for about 4 half-hourly ticks. Each read takes up to 2 minutes, and a pass makes at most 3. This uses Actions minutes and GitHub API rate budget only, with no billing change. |
The earlier-fail-read mark never reached the next tick: the dispatch workflow restores no record on a sweep tick and saves the tick's record under main's commit, which no run restores (advisory on #12843). Persisting it would take two workflow cache steps and a cache-trust surface to save a few bounded re-reads. The mark and the tick's save of it are gone. The unsent sweep sends a head with no unresolved thread and a fail note from another head only while its newest review run completed within EARLIER_FAIL_WINDOW_MS (2 hours), under the EARLIER_FAIL_READS cap per pass. A failed read stays distinct from "no earlier FAIL" and is retried there; past the window the head is left alone. Refs: #12809 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Closes #12809
Summary
The merge gate refuses a lanes App pull request
verify:earlier-failwhen a FAIL at another head has the same change digest. Once that FAIL's fail-note threads were resolved, the PR could not get out of that state. address-feedback found no thread, endedsuccesswithout pushing, and the dispatcher kept re-running the same no-op round (#12783, runs 41606722334 and 41610279787).This change implements the decision record. The reader's
earlierFailsnames the FAILed heads, the dispatcher sends a model round for them even when no thread is unresolved, and the select job reads that FAIL's notes from its verdict artifact. A round that pushes nothing and reports every note already addressed now escalates the newfeedback-already-addressedreason with theneeds-sessionlabel instead of endingsuccess.Fix
verify-cap.mjs: on averify:earlier-failrefusal, the reader's answer carriesearlierFailHeads, taken from the report's existingearlierFails(only commit ids other than the head; anything else names no head and warns). That refusal no longer warns.feedback-dispatch.mjs: on a lanes App PR whose threads hold a fail note from another head, the "no unresolved review thread" skip now waits for the verifier read, at mostEARLIER_FAIL_READS(3) such reads per pass. On an earlier-fail answer it sends a model round with the newverify-earlier-fail-headsinput (space-separated SHAs), never on a pass-only round. The cap, hold, owner and handled-check-run skips still come first.pr-unblock.yml: the new input sits besideverify-pass-headandverify-superseded-head.address-feedback-selectgainsactions: readand theVERIFY_EARLIER_FAIL_HEADSenv var.address-feedback.mjsselect: when no thread is kept, nothing untaken is open and FAILed heads were named, it reads their notes (newearlier-fail-notes.mjs) intothreads.jsonverify_notes(VNOTE_<n>items, each with a head, a path and a body). Before reading, it stands downawaiting-sessionwhile that head's stop is queued for a session builder, and it keeps the protected-path stop and the round cap. A read that fails escalatesfeedback-verdict-unreadable(routefail).earlier-fail-notes.mjs: finds the FAIL by its exact-name index artifact for the head (pr-verify-index-sha-<head>), then the run's FAIL verdict for this PR and head. It reads only runs thatresolve-pr.mjscountedByaccepts (pr-verify.yml on main of this repo) and passes over others, as the gate's reader does. The verdict goes throughverify-notes.mjsreadVerdictandnoteComments. Every other failure, a withheld FAIL, and more than 50 items all throw.feedback-thread.shgainsnotes.recordacceptsVALIDoralready-addressed(with evidence) for aVNOTE_id, and a note's path is in scope like a thread's.comments-ci.md, the SKILL.md mode row and the collect-base-activity preamble name the new subcommand.apply-patch.mjskeptThreadsFromcounts notes as threads on their paths, so the fix scope and theLane-Threadtrailer name fixed notes.publish-feedback.mjssettleNotesposts and resolves nothing for notes.escalationReason: "").feedback-already-addressed.feedback-not-landed.feedback-incomplete, and an off-template reply escalatesfeedback-invalid.report-escalation.test.shlist both new reasons. The changes include pr-pipeline README text and two changelog fragments, forsource-controlandpr-pipeline.Verification
earlierFailsalready exists atbabysit_verify_evidence.py:1132. New testtest_an_earlier_fail_refusal_names_the_failed_head(test_babysit_verify_evidence.py:1287).verify-cap.mjs:250passes the heads on, tested atverify-cap.test.mjs:142.feedback-dispatch.mjs:751and read byaddress-feedback.mjs:1157. The artifact read isearlier-fail-notes.mjs:68-94(countedBy,readVerdict,noteComments). Tests:earlier-fail-notes.test.mjs:60,address-feedback.test.mjs:2081.feedback-dispatch.mjs:622and:737, behind the prefilter and cap below. Testfeedback-dispatch.test.mjs:1557.stop-resolvedaddress-feedback.mjs:1008. Testaddress-feedback.test.mjs:2081.publish-feedback.mjs:639(settleNotes). Testpublish-feedback.test.mjs:1790.needs-session, notsuccesspublish-feedback.mjs:652. Testpublish-feedback.test.mjs:1801.address-feedback.mjs:1017(feedback-verdict-unreadable, routefail). Testsaddress-feedback.test.mjs:2103andearlier-fail-notes.test.mjs:82,91,113.whythat names the already-addressed earlier FAILescalation-routes.json:27. Thewhyis checked atpublish-feedback.test.mjs:1801, andreport-escalation.test.sh:185pins the table.babysit_verify_evidence.py. Its suite passes (430 tests).address-feedback.test.mjs:2081andearlier-fail-notes.test.mjs:60.publish-feedback.test.mjs:1801,:1813. The same-head re-dispatch stands down ataddress-feedback.test.mjs:2137.Local runs:
node --testover address-feedback, earlier-fail-notes, feedback-dispatch, publish-feedback, verify-cap, callers, apply-patch, collect-base-activity and workflow-gates: 1364 passed, 0 failed.feedback-thread.test.shpasses all 164 cases.report-escalation.test.shpasses.babysit_verify_evidenceunittest: 430 tests OK.scripts/check-affected.sh --base origin/main --no-fetch: 15 passed, 0 skipped.Beyond the record, each choice taken to meet a criterion:
feedback-verdict-unreadableis the new reason that gives criterion 7 its failing status, with its lane-stopped check.feedback-verifier-open.VALIDoralready-addressed.pr-run-activity-write.yml(the reason allowlist) and the collect-base-activity preamble are edited.Cost bound: one verifier read can take up to
VERIFY_CAP_TIMEOUT_MS, 2 minutes (verify-cap.mjs:67), andsweepStaleandrecoverLostdecide PR after PR inside one 10-minute dispatch job. So the no-unresolved-thread path reads only when both of these hold:<!-- pr-verify:fail-note -->) from a review of another head (feedback-dispatch.mjs:788earlierFailNoted). This costs no extra call. Every other PR keeps the free skip. Thepr-verify-failedlabel was not usable as the sign: verify-notes.mjs puts it only on a person's PR, never a lanes App PR. A failed pr-verify check at the head was not usable either: in an earlier-fail refusal the FAIL is at another head. The cost of this choice: a FAIL whose notes were never posted as threads (the PR moved before post-verify-notes ran) gets no round on this path.EARLIER_FAIL_READS(3,feedback-dispatch.mjs:783) such reads, through a budget shared across the pass's sweeps. The rest skip unrecorded.readFailed(verify-cap.mjs), and a cap read that could not count every FAIL is treated the same way; neither is taken as "no earlier FAIL". A head left by a failed read or by the cap has no review run coming, sosweepUnsentalso sends a head with no unresolved thread that holds a fail note from another head, but only while its newest review run completed withinEARLIER_FAIL_WINDOW_MS(2 hours, besideEARLIER_FAIL_READSinfeedback-dispatch.mjs). Nothing records an answer, so a head answered "no earlier FAIL" may be read again inside the window: about 4 half-hourly ticks, and never more than the 3-read cap per pass.pr-feedback-dispatch.yml:396-405skips the restore forpr-unblock-dispatch.yml, and the save keys on main's commit on a tick), so a recorded answer never reached the next tick. Persisting it would add two workflow steps (a rolling cache restore and save) and a cache-trust surface (any default-branch job can write the cache) to save at most about 4 bounded re-reads per head.feedback-dispatch.test.mjs): "a thread-less head whose verifier read failed is read again by a later pass inside the window", and "an answered thread-less head may be re-read inside the window, never past the pass's cap, and not at all past the window".Tests:
feedback-dispatch.test.mjscovers both: no fail-note sign means no verifier read (feedback-dispatch.test.mjs:1607), and 4 candidates in one pass get 3 reads (:1626).Related
.github/workflows/pr-feedback-dispatch.yml. This PR does not touch that file: the dispatcher builds its inputs infeedback-dispatch.mjs.successaddress-feedback check runs, so this change re-dispatches it only once its head moves.Known limits
verify:earlier-fail, the dispatcher skips the PR as thread-less, and no round or escalation follows, as before this change. This is accepted for the case pr-unblock: address-feedback dead-ends on verify:earlier-fail once fail notes are resolved #12809 describes, where the fail-note threads exist and are resolved. A follow-up issue is filed only if a run shows the edge.EARLIER_FAIL_WINDOW_MS(2 hours after its newest review run completed), or that the 3-read cap keeps leaving that long, is left alone: no sweep reads it again until a new review run at the head, such as the one a push or a base merge starts.🤖 Generated with Claude Code