From be2b5aef3d9448dfae054d686e15954677c9d46f Mon Sep 17 00:00:00 2001 From: Madhura68 Date: Sat, 9 May 2026 16:18:20 +0200 Subject: [PATCH] fix(cleanup): keepBranch + sprint-scope siblings voor SPRINT pr_strategy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Symptoom: in een sprint met pr_strategy=SPRINT (5 tasks, 3 stories) werden de eerste twee tasks SKIPPED door Claude (werk al in main na een externe PR). De derde task crashte op: git worktree add /home/agent/.scrum4me-agent-worktrees/ feat/sprint-uhrbtc8z fatal: invalid reference: feat/sprint-uhrbtc8z Root cause: cleanupWorktreeForTerminalStatus checkte op active siblings binnen dezelfde **story** + verwijderde de branch bij keepBranch=false. Voor SPRINT pr_strategy delen alle stories in de sprint één branch (feat/sprint-). Eerste task SKIPPED, story ST-1304 had geen actieve siblings meer (T-807 was ook al SKIPPED), branch werd verwijderd. T-808 in story ST-1305 wilde reuse'n maar branch bestond niet meer. Fix: 1. Sibling-check verbreden voor SPRINT pr_strategy: kijk naar alle actieve jobs in dezelfde sprint_run_id (niet alleen story_id). 2. keepBranch=true voor SKIPPED bij SPRINT pr_strategy: andere stories in dezelfde sprint hebben de branch nog nodig. Tests: 341 passed (38 files). Typecheck OK. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/tools/update-job-status.ts | 54 +++++++++++++++++++++++++--------- 1 file changed, 40 insertions(+), 14 deletions(-) diff --git a/src/tools/update-job-status.ts b/src/tools/update-job-status.ts index 6b4680f..5e40988 100644 --- a/src/tools/update-job-status.ts +++ b/src/tools/update-job-status.ts @@ -71,31 +71,57 @@ export async function cleanupWorktreeForTerminalStatus( return } - // Branch-per-story: only remove the worktree if no sibling job in the same - // story is still active. If siblings are queued/claimed/running they will - // re-use this branch — destroying the worktree now wastes the next claim. + // Branch-shared check: bepaal welke siblings dezelfde branch reuse'n. + // - SPRINT pr_strategy → alle TASK_IMPLEMENTATION jobs in dezelfde + // sprint_run delen feat/sprint-. + // - STORY pr_strategy / legacy → alle TASK_IMPLEMENTATION jobs in + // dezelfde story delen feat/story-. + // Bij active siblings: defer cleanup (en in elk geval keepBranch=true) + // zodat de volgende claim de branch kan reuse'n. const job = await prisma.claudeJob.findUnique({ where: { id: jobId }, - select: { task: { select: { story_id: true } } }, + select: { + task: { select: { story_id: true } }, + sprint_run_id: true, + sprint_run: { select: { pr_strategy: true } }, + }, }) - if (job?.task) { - const activeSiblings = await prisma.claudeJob.count({ + + let activeSiblings = 0 + let scope = '' + if (job?.sprint_run && job.sprint_run.pr_strategy === 'SPRINT') { + activeSiblings = await prisma.claudeJob.count({ + where: { + sprint_run_id: job.sprint_run_id, + status: { in: ['QUEUED', 'CLAIMED', 'RUNNING'] }, + id: { not: jobId }, + }, + }) + scope = `sprint_run ${job.sprint_run_id}` + } else if (job?.task) { + activeSiblings = await prisma.claudeJob.count({ where: { task: { story_id: job.task.story_id }, status: { in: ['QUEUED', 'CLAIMED', 'RUNNING'] }, id: { not: jobId }, }, }) - if (activeSiblings > 0) { - console.log( - `[update_job_status] cleanup deferred for job=${jobId}: ${activeSiblings} sibling(s) still active in story ${job.task.story_id}`, - ) - return - } + scope = `story ${job.task.story_id}` } - // Keep branch when job is done and a branch was reported (agent pushed) - const keepBranch = status === 'done' && branch !== undefined + if (activeSiblings > 0) { + console.log( + `[update_job_status] cleanup deferred for job=${jobId}: ${activeSiblings} sibling(s) still active in ${scope}`, + ) + return + } + + // Keep branch when: + // - job is done en agent rapporteerde push (branch !== undefined), of + // - SPRINT pr_strategy job is skipped — andere stories delen branch. + const keepBranch = + (status === 'done' && branch !== undefined) || + (status === 'skipped' && job?.sprint_run?.pr_strategy === 'SPRINT') try { await removeWorktreeForJob({ repoRoot, jobId, keepBranch }) } catch (err) {