Repository navigation
fix(pr-fix-queue): refuse FIXED on no-progress and reopen stale tombstones - #942
Conversation
…tones (#940) The PR-fix queue trusted any successful workload exit as a fix and closed the item as FIXED. When the workload ran without pushing anything (misospace/ miso-gallery#467), the PR kept CHANGES_REQUESTED and the FIXED tombstone stranded it: re-detection wrote more enqueue rows but never re-dispatched, and the only recovery was a two-call sequence that lied about the state. 1. Record `headSha` at enqueue: the pr-followup sync and webhook now capture `head.sha` from the PR list/detail payload and thread it into the ingestion event and PrFixQueueItem.headSha. 2. Verify before FIXED: `markPrFixItem` runs a head SHA comparison via the new `assertPrHeadMovedForFix` helper; if unchanged it rolls the item back to QUEUED with an audit note instead of writing the untrusted tombstone. Legacy rows without headSha and transient GitHub failures degrade to accept (don't strand, don't pretend the guard ran). 3. Reopen stale FIXED on re-detection: `enqueuePrFixItem` reopens an item when same evidence is re-detected with the same recorded head SHA, the safety net for the worked example (271 re-detections vs 1 mark row). 4. One-call recovery: `requeuePrFixItem` now accepts FIXED items, removing the "mark BLOCKED first to lie about state" workaround. Tests cover head SHA capture, the guard (pass / no-record / unchanged / unavailable), the reopen path (both the worked example and the new-head-SHA companion case), and the FIXED-requeue path. Fixes #940 Signed-off-by: Saffron <263493777+itsmiso-ai@users.noreply.github.com>
…t list The facade test asserts github-prs' exact export list, and the new fetchPullRequestHeadSha was not added to it. Only failure in the suite. 2562 passing, typecheck clean; the four lint warnings are identical on main.
There was a problem hiding this comment.
AI Automated Review
Full PR review.
Analysis engine: MiniMax-M2.7@https://litellm.jory.dev/v1 (anthropic) — primary route
Recommendation: Approve
This PR correctly implements the three substantive fixes from PR 940. The work is well-structured, thoroughly tested, and handles all edge cases appropriately. CI is green across all completed checks (Tests was pending at time of analysis).
Change-by-Change Findings
New helper: fetchPullRequestHeadSha (src/lib/github-prs.ts)
Fetches the current PR head SHA from the per-PR detail endpoint. Returns null on network failure or missing head.sha. Called by markPrFixItem during the FIXED transition to compare against the recorded head.
headSha capture at sync time (src/app/api/pr-followup/sync/route.ts)
The GithubPR interface now extends Omit<GithubPRBase, "head"> and redefines head to include sha. prHeadSha = pr.head?.sha ?? null threads the value through all event types (comment, review_comment, review, check_run, merge_state, merge_conflict). This is the canonical capture point—GitHub's pulls?state=open returns head.sha on every entry.
headSha capture at webhook time (src/app/api/pr-followup/webhook/route.ts)
Webhook event parsing also captures headSha. Note the explicit null for check_run events (GitHub doesn't include head.sha on that payload): the FIXED guard degrades gracefully for that path.
PrFollowupEvent interface (src/lib/pr-followup-ingestion.ts)
headSha?: string | null added to the event interface. ingestEvent and ingestMergeConflict now forward it to enqueuePrFixItem.
Core fix logic (src/lib/pr-fix-queue.ts)
enqueuePrFixItem: When re-detecting known evidence on aFIXEDitem whoseheadShamatches the newheadSha, setsreopenFixStale = trueand flips the item back toQUEUED. History records a#940-tagged note explaining why.assertPrHeadMovedForFix: Returns"passed" | "no-record" | "head-unchanged" | "head-unavailable". Best-effort by design—refuses on"head-unchanged", accepts on"no-record"and"head-unavailable".markPrFixItem: On FIXED transition, runs the guard. If"head-unchanged", rolls back toQUEUEDwith a history note.requeuePrFixItem: Now acceptsFIXEDitems in addition toBLOCKED(single-call recovery instead of two). History notes "PR 940 recovery" when reopening from FIXED.
Linked Issue Fit
| Issue PR 940 Point | Status | Verification |
|---|---|---|
1. Record headSha at enqueue |
✅ Implemented | prHeadSha captured at sync, webhook, and ingestion layers; threaded into all event types |
| 2. Verify before marking FIXED | ✅ Implemented | assertPrHeadMovedForFix + guard in markPrFixItem; refuses FIXED when head unchanged |
| 3. Reopen stale FIXED tombstones | ✅ Implemented | enqueuePrFixItem reopens FIXED when same evidence + same headSha re-detected |
| 4. Optional: requeue accepts FIXED directly | ⚪ Not addressed | Issue called it "optionally"; PR body states three substantive points |
Standards Compliance
The implementation follows all documented conventions:
- Error handling:
assertPrHeadMovedForFixusescatchwitherror instanceof Error; GitHub fetch failures are best-effort (warn and accept) rather than blocking. - Validation: Status normalization via
normalizePrFixStatusbefore transitions. - No commit of secrets: No secrets or env file changes.
- Prisma: No schema changes in this PR;
headShais an existing nullable column.
CI Results
All completed checks pass: Docker Build (MCP), Docker Build, Build, Lint, Database migrations, Coverage, Database integration, Typecheck, npm audit, smoke. Tests was still pending at analysis time, consistent with the author's reported "2562 passed | 5 skipped".
Edge Cases Covered
| Scenario | Expected Behavior | Test? |
|---|---|---|
| FIXED with head moved | Accepts FIXED | ✅ |
| FIXED with head unchanged | Refuses, rolls back to QUEUED | ✅ |
| Legacy row (no headSha) | Accepts FIXED (cannot compare) | ✅ |
| GitHub fetch failure | Accepts FIXED (best-effort) | ✅ |
| GitHub returns null headSha | Accepts FIXED (best-effort) | ✅ |
| Non-FIXED transitions | No GitHub round-trip | ✅ |
| FIXED item reopened via requeue | Works as single call | ✅ |
| STALE item requeue | Still rejected (PR is gone) | ✅ |
| check_run webhook (no headSha) | Guard skipped for that path | ✅ (code comment) |
Closes #940.
Why this PR exists
Foreman produced this work and then threw it away. The coder returned
GO(turnCount158,succeededOnTarget: true) and the reviewer returnedAPPROVE— but the verdict was rewritten toNO-GOby theissueAskrail:That
claimedstring is item 1 of #940's own "Suggested shape" list, and the branch implements exactly it —headSha: prHeadShaat four call sites. The reviewer opens a PR only onGO, so none was opened; the bridge then retried the whole Workload three times (each attempt identical by construction, since re-running cannot make an unverifiable ask verify) and parked the issue asneeds-human.Opening it by hand so 450 lines of correct work are not lost. Root causes filed separately: misospace/foreman-dispatch-bridge#287 for the wasted retry budget, defilantech/LLMKube#1760 for the demotion itself.
What the change does
Addresses #940's three substantive points:
headShaat enqueue (src/lib/pr-followup-ingestion.ts,src/app/api/pr-followup/sync/route.ts,src/app/api/pr-followup/webhook/route.ts), so a laterFIXEDtransition has something to compare against. Previously the column was always null.FIXEDwhen the PR head has not moved (src/lib/pr-fix-queue.ts) — a fix Workload that exits successfully without advancing the PR is not a fix.FIXEDwhose evidence is still being re-detected goes back in the queue instead of accumulating anotherenqueuerow.New helper
fetchPullRequestHeadShainsrc/lib/github-prs.ts.What I changed on top
One commit:
fetchPullRequestHeadShawas missing from the pinned export list insrc/lib/github-facades.test.ts, which was the single failing test in the suite. Nothing else touched.Verification
I checked the lint warnings against
origin/maindirectly rather than assuming; same four.Worth reviewing carefully
The coder ran to
turnCount158 against amaxTurnsof 160, so it finished close to the ceiling. I have reviewed the diff and run the suite, but a change that nearly exhausted its budget deserves a closer read than one that converged early.Assisted-by: Claude Code (diagnosed the discarded verdict, fixed the failing facade test, verified the suite and opened this PR; the implementation is foreman's).