fix(issues): restore pre-checkout status when a run releases without advancing (BLO-20649) - #1037
fix(issues): restore pre-checkout status when a run releases without advancing (BLO-20649)#1037allyblockcast[bot] wants to merge 2 commits into
Conversation
|
@ally please review at head 34380a3 — BLO-20649, restoring issue status when a run releases its checkout without advancing the issue. Review focus, in priority order:
One thing I want challenged: the |
|
Hey @allyblockcast[bot]! Before this PR can be reviewed, a few things need attention: Missing or incomplete:
Once updated, push a new commit and these checks will re-run automatically. — commitperclip |
Ally — Consolidated PR ReviewLenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex. Important Issues (3)
Strengths
Recommended Action
This PR is authored by |
kkroo
left a comment
There was a problem hiding this comment.
Reviewed after the checkoutRestoreStatus projection fix. The issue list select now includes the field required by the shared issue-list contract, and the targeted server build passed locally after rebuilding shared/plugin-sdk prerequisites.
Ally — Consolidated PR ReviewLenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex. Prior Findings Dispositioned (3)
Important Issues (3)
Strengths
Recommended Action
This PR is authored by |
…advancing `checkout` promotes an issue to `in_progress` on entry, but every lock-release path cleared only the execution-lock columns and left `status` behind. So `in_progress` was not a statement about current work — it was a high-water mark of every issue any wake had ever touched, and it only ever came back down by hand. Measured on the CTO queue: 124 issues `in_progress`, 96 of them with no `executionRunId`, no lock and no monitor; of 46 hand-demoted one day, 22 were back within 21h. Record the pre-checkout status in a new `issues.checkout_restore_status` column, captured inside checkout's own UPDATE (Postgres reads the old tuple in SET), so the restore is exact rather than guessed — a `backlog` issue returns to `backlog`, not `todo`. `restoreCheckoutPromotedStatus` is one guarded statement, safe to call inside the caller's existing transaction. It no-ops unless the issue is still `in_progress`, a restore marker is present, and neither `checkout_run_id` nor `execution_run_id` points at a live run. Any explicit status write clears the marker, which is what keeps both "the run advanced the issue" and a deliberate `in_progress` write from being clobbered. Wired into the release paths that strand a status: - issues.ts clearExecutionRunIfTerminal / clearCheckoutRunIfTerminal - recovery/service.ts run finalize - heartbeat.ts scheduled-retry gate cancel Rows stranded before this change carry no marker; re-checkout adopts them with a `todo` marker so the existing backlog drains instead of needing hand-demotion. Fixes BLO-20649.
7a2a20e to
b7b7db4
Compare
Thinking Path
Linked Issues or Issue Description
Searched open PRs for overlap. Adjacent but distinct — all concern who holds a lock, none reset the promoted status: #910 (bind locks only for running runs), #906 (bound non-live execution-lock ownership), #911 (never-started run locking out its assignee), #960 (re-read lock ids across handover). No duplicate found.
What Changed
0210_issue_checkout_restore_status.sql+ schema: new nullableissues.checkout_restore_status, holding the status the row had immediately before checkout promoted it.NULL= no promotion to undo.checkoutpopulates it inside its own UPDATE —case when status = 'in_progress' then coalesce(checkout_restore_status, 'todo') else status end— relying on Postgres reading the pre-update tuple inSET, so there is no read-modify-write race.server/src/services/issue-checkout-status.tswithrestoreCheckoutPromotedStatus(dbOrTx, issueId): a single guarded statement, safe to call inside a caller's existing transaction. It no-ops unless the issue is stillin_progress, a marker is present, and neithercheckout_run_idnorexecution_run_idpoints at a live run. It also owns the canonical terminal-run-status list, whichissues.tsnow re-exports.issueService.updateclears the marker on any explicit status write — this is what makes both "the run advanced it" and a deliberatein_progresswrite non-clobberable.clearExecutionRunIfTerminal,clearCheckoutRunIfTerminal(issues.ts), run finalize (recovery/service.ts), scheduled-retry gate cancel (heartbeat.ts).server/src/__tests__/issues-service.test.ts.Deliberately not changed: checkout still sets
in_progress. Marking real work in flight is correct — the missing half was the release.Verification
The 7 new cases map one-to-one onto the issue's acceptance criteria:
todo→ checkout → releasetodobacklog→ checkout → releasebacklog, nottodoin_reviewin_progressrunningtodoon next checkout+releaseMigration gates, run locally:
check-migration-numberingrc=0;check-migration-safetyrc=0 ("23 historical finding(s) covered by baseline").Post-deploy signal: issues with
status='in_progress' AND execution_run_id IS NULLshould trend to ≈0 and stay there without manual triage. Baseline before this fix 96; immediately after a manual sweep 1; if the leak were still live it climbs back into the dozens within 24h.Risks
ADD COLUMN, no default, no backfill, no rewrite; safe to apply ahead of the code and harmless to roll back.in_progresswithout an explicit status write would be demoted to its restore value after its next wake releases. I believe this is the right trade — it is exactly the drain that makes the ~96 existing strands self-clear — but it is the judgement call most worth a reviewer's disagreement.restoreCheckoutPromotedStatus's live-run check is wrong, a status could be reset out from under an executing run. It checks both lock columns, not just the one the caller cleared, and relies onx IN (NULL)being NULL rather than true. Worth adversarial reading.executionRunId: nullsites across the three service files; 5 are wired here. The rest either set a status explicitly already or hand the lock to a retry. A missed path means the leak continues at a lower rate — quieter than the current failure, so please sanity-check that judgement.Model Used
Claude Opus 5 (
claude-opus-5[1m], 1M context), extended thinking, with tool use and code execution — run as the CTO agent in Paperclip.Checklist
Fixes: #/Closes #/Refs #OR (b) described the issue in-PR following the relevant issue template