PBI-49 P0: resumePausedSprintRunAction — DONE bij scope-completed (#138)

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) <noreply@anthropic.com>
This commit is contained in:
Janpeter Visser 2026-05-07 11:11:45 +02:00 committed by GitHub
parent d3e79021c1
commit e6dcc91383
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -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({ const activeClaims = await tx.claudeJob.count({
where: { sprint_run_id, status: { in: ['CLAIMED', 'RUNNING'] } }, 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({ await tx.sprintRun.update({
where: { id: sprint_run_id }, where: { id: sprint_run_id },
data: { data: {
status: activeClaims > 0 ? 'RUNNING' : 'QUEUED', status: nextStatus,
pause_context: Prisma.JsonNull, 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) { if (result.ok && 'sprint_id' in result) {