From 5e55bce34f1f8fdbc0e8c133551ed8bd05fa8932 Mon Sep 17 00:00:00 2001 From: Madhura68 Date: Thu, 7 May 2026 11:02:31 +0200 Subject: [PATCH] =?UTF-8?q?PBI-49=20P0:=20resumePausedSprintRunAction=20?= =?UTF-8?q?=E2=80=94=20DONE=20bij=20scope-completed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A STORY-mode MERGE_CONFLICT triggers AFTER all tasks are already DONE (storyBecameDone is what produces the conflict event). The previous resume logic put the SprintRun back to QUEUED, but there was no QUEUED job left for a worker to claim — the run would hang forever. Now: when both QUEUED and CLAIMED/RUNNING counts are zero, transition straight to DONE (with finished_at set). The dev resolved the conflict manually and the PR is theirs to merge. Existing behaviour preserved when active claims or queued work remain. Co-Authored-By: Claude Opus 4.7 (1M context) --- actions/sprint-runs.ts | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/actions/sprint-runs.ts b/actions/sprint-runs.ts index aa9a709..18a9f86 100644 --- a/actions/sprint-runs.ts +++ b/actions/sprint-runs.ts @@ -280,21 +280,39 @@ export async function resumePausedSprintRunAction( }) } - // RUNNING when there's still a claim active, otherwise QUEUED so the - // worker picks up the next job on its next claim. const activeClaims = await tx.claudeJob.count({ where: { sprint_run_id, status: { in: ['CLAIMED', 'RUNNING'] } }, }) + const queuedJobs = await tx.claudeJob.count({ + where: { sprint_run_id, status: 'QUEUED' }, + }) + + // PBI-49 P0: a STORY auto-merge MERGE_CONFLICT lands AFTER all tasks are + // already DONE (storyBecameDone fires the conflict). Going back to QUEUED + // would hang the SprintRun forever — there is no QUEUED job to claim. + // When the scope is fully complete, transition straight to DONE; the + // dev resolved the conflict manually and the PR is theirs to merge. + let nextStatus: 'RUNNING' | 'QUEUED' | 'DONE' + let finishedAt: Date | undefined + if (activeClaims === 0 && queuedJobs === 0) { + nextStatus = 'DONE' + finishedAt = new Date() + } else if (activeClaims > 0) { + nextStatus = 'RUNNING' + } else { + nextStatus = 'QUEUED' + } await tx.sprintRun.update({ where: { id: sprint_run_id }, data: { - status: activeClaims > 0 ? 'RUNNING' : 'QUEUED', + status: nextStatus, pause_context: Prisma.JsonNull, + ...(finishedAt ? { finished_at: finishedAt } : {}), }, }) - return { ok: true as const, sprint_id: run.sprint_id } + return { ok: true as const, sprint_id: run.sprint_id, finalStatus: nextStatus } }) if (result.ok && 'sprint_id' in result) {