From 0a18f565d27ef2e051ff8756dc2bed41910a2262 Mon Sep 17 00:00:00 2001 From: Madhura68 Date: Sat, 9 May 2026 13:53:43 +0200 Subject: [PATCH] fix(update_job_status): gebruik DB-branch ipv legacy feat/job-<8> fallback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Symptoom: TASK_IMPLEMENTATION job T-806 in een SPRINT-strategy sprint faalde met: push failed (unknown): error: src refspec feat/job-us3aqoup does not match any error: failed to push some refs to 'https://github.com/.../Scrum4Me.git' Maar de PR was wel succesvol aangemaakt door Claude (PR #174 op feat/sprint-fvy30lvv) — Claude commit'te in de juiste worktree-branch, maar update_job_status's prepareDoneUpdate probeerde te pushen op een niet-bestaande branch. Root cause: prepareDoneUpdate(jobId, branch) accepteert een branch-arg (meestal undefined want Claude geeft 'm niet mee) en valt terug op `feat/job-${jobId.slice(-8)}`. Dat is het legacy pre-PBI-50 pad — voor sprint-jobs is de werkelijke branch `feat/sprint-` (PR_strategy=SPRINT) of `feat/story-` (STORY), opgeslagen in ClaudeJob.branch door attachWorktreeToJob. Fix: - prepareDoneUpdate leest nu eerst ClaudeJob.branch uit de DB als de expliciete branch-arg ontbreekt. - Pas daarna fallback op `feat/job-<8>` (zou niet moeten voorkomen na PBI-50). Tests: vi.mock('../src/prisma.js') toegevoegd voor de findUnique-stub. Bestaande test "derives branchName from jobId when branch is undefined" hernoemd naar "reads branchName from DB" met DB-mock returnt 'feat/sprint-fvy30lvv'. Plus extra test voor de legacy fallback wanneer DB.branch ook null is. 341 tests in 38 files passed (was 340, +1). Co-Authored-By: Claude Opus 4.7 (1M context) --- __tests__/update-job-status-push.test.ts | 32 +++++++++++++++++++++++- src/tools/update-job-status.ts | 18 ++++++++++++- 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/__tests__/update-job-status-push.test.ts b/__tests__/update-job-status-push.test.ts index 1232670..3ffd6b3 100644 --- a/__tests__/update-job-status-push.test.ts +++ b/__tests__/update-job-status-push.test.ts @@ -5,13 +5,26 @@ vi.mock('../src/git/push.js', () => ({ pushBranchForJob: vi.fn(), })) +vi.mock('../src/prisma.js', () => ({ + prisma: { + claudeJob: { + findUnique: vi.fn(), + }, + }, +})) + import { pushBranchForJob } from '../src/git/push.js' +import { prisma } from '../src/prisma.js' import { prepareDoneUpdate } from '../src/tools/update-job-status.js' const mockPush = pushBranchForJob as ReturnType +const mockFindUnique = (prisma as unknown as { + claudeJob: { findUnique: ReturnType } +}).claudeJob.findUnique beforeEach(() => { vi.clearAllMocks() + mockFindUnique.mockResolvedValue(null) }) describe('prepareDoneUpdate', () => { @@ -39,8 +52,25 @@ describe('prepareDoneUpdate', () => { }) }) - it('derives branchName from jobId when branch is undefined', async () => { + it('reads branchName from DB (claudeJob.branch) when branch arg is undefined', async () => { process.env.SCRUM4ME_AGENT_WORKTREE_DIR = '/wt' + mockFindUnique.mockResolvedValue({ branch: 'feat/sprint-fvy30lvv' }) + mockPush.mockResolvedValue({ pushed: true, remoteRef: 'refs/heads/feat/sprint-fvy30lvv' }) + + await prepareDoneUpdate('job-abc12345', undefined) + + expect(mockFindUnique).toHaveBeenCalledWith({ + where: { id: 'job-abc12345' }, + select: { branch: true }, + }) + expect(mockPush).toHaveBeenCalledWith( + expect.objectContaining({ branchName: 'feat/sprint-fvy30lvv' }), + ) + }) + + it('falls back to feat/job-<8> when neither branch arg nor DB.branch is set', async () => { + process.env.SCRUM4ME_AGENT_WORKTREE_DIR = '/wt' + mockFindUnique.mockResolvedValue({ branch: null }) mockPush.mockResolvedValue({ pushed: true, remoteRef: 'refs/heads/feat/job-abc12345' }) await prepareDoneUpdate('job-abc12345', undefined) diff --git a/src/tools/update-job-status.ts b/src/tools/update-job-status.ts index 5a75a7d..6b4680f 100644 --- a/src/tools/update-job-status.ts +++ b/src/tools/update-job-status.ts @@ -119,9 +119,25 @@ export async function prepareDoneUpdate( jobId: string, branch: string | undefined, ): Promise { + // Resolve branch in deze volgorde: + // 1. Expliciete `branch`-arg van Claude (meestal niet meegegeven). + // 2. ClaudeJob.branch uit de DB — gezet door attachWorktreeToJob met de + // juiste pr_strategy: feat/sprint- voor SPRINT, feat/story- + // voor STORY met sibling-reuse. + // 3. Legacy fallback feat/job-<8> — alleen voor jobs zonder DB-branch + // (zou niet moeten voorkomen na PBI-50). + let resolvedBranch = branch + if (!resolvedBranch) { + const dbJob = await prisma.claudeJob.findUnique({ + where: { id: jobId }, + select: { branch: true }, + }) + resolvedBranch = dbJob?.branch ?? undefined + } + const branchName = resolvedBranch ?? `feat/job-${jobId.slice(-8)}` + const worktreeDir = getWorktreeRoot() const worktreePath = path.join(worktreeDir, jobId) - const branchName = branch ?? `feat/job-${jobId.slice(-8)}` const pushResult = await pushBranchForJob({ worktreePath, branchName })