--- title: "Worker idempotency & job-status protocol" status: active audience: [ai-agent, contributor] language: nl last_updated: 2026-05-05 when_to_read: "Vóór het implementeren of debuggen van Claude-CLI-worker logica die `update_job_status` aanroept." --- # Worker idempotency & job-status protocol Beschrijft hoe de Scrum4Me-worker `ClaudeJob.status` moet zetten op basis van `VerifyResult` × git-diff-staat × branch-staat. Doel: voorkom status-divergentie zoals geconstateerd in de **PBI-33 batch (5-5-2026 22:22)** waarin werk dat al gemerged was via PR #102/#103/#104 leidde tot inconsistente combinaties van `verify=EMPTY → FAILED` en `verify=DIVERGENT → DONE`. --- ## Beslissingsboom Aan het einde van een story-job, ná `verify`-pass: | `verify_result` | netto diff t.o.v. `origin/main` | branch al gemerged | → `ClaudeJob.status` | `Task.status` | |---|---|---|---|---| | `ALIGNED` of `PARTIAL` | nieuwe commit aanwezig | n.v.t. | **`DONE`** | `DONE` | | `EMPTY` | leeg (niets gewijzigd) | werk zit al op `origin/main` | **`SKIPPED`** | `DONE` | | `EMPTY` | leeg, maar werk staat **niet** op origin | n.v.t. | **`FAILED`** (`error: "verify produced no output"`) | `IN_PROGRESS` (handmatig onderzoeken) | | `DIVERGENT` | aanwezig, maar identiek aan al-gemergde branch | ja (PR closed/merged) | **`SKIPPED`** | `DONE` | | `DIVERGENT` | aanwezig, niet matchend met main | nee | **`FAILED`** (`error: "verify divergent — handmatige review"`) | `IN_PROGRESS` | | (compile-fail, test-fail, push-fail, exception) | n.v.t. | n.v.t. | **`FAILED`** met concrete `error` | `IN_PROGRESS` | | (gebruiker drukt cancel) | n.v.t. | n.v.t. | **`CANCELLED`** | `TO_DO` | ### Vuistregels - **`SKIPPED`** = "geen netto-output, maar geen fout" — werk was al gedaan vóór deze job draaide. Task mag op `DONE` omdat het beoogde resultaat in main aanwezig is. - **`FAILED`** is gereserveerd voor échte fouten: code-fouten, test-failures, push-fouten, onverklaarde diff. Niet voor "implementatie was al gedaan". - **`DONE`** alleen bij `ALIGNED`/`PARTIAL` mét nieuwe commit op de feature-branch. Een lege `DIVERGENT` op een al-gemergde branch is géén `DONE`. --- ## StoryLog-verplichting Tijdens elke job moet de worker `story_logs`-entries schrijven via de MCP-tools, anders is de Sync-tab leeg: | Wanneer | MCP-tool | Inhoud | |---|---|---| | Bij claim | `log_implementation` | "Start implementatie van T-XXX. Branch X. Plan: …" | | Per commit | `log_commit` | hash + message + samenvatting van wijzigingen | | Na verify | `log_test_result` | status `PASSED` of `FAILED` + samenvatting van checks | In **PBI-33 batch** zijn deze tools **niet** aangeroepen — `story_logs` voor ST-1208/1209/1210 is leeg. Worker MAG geen job afronden zonder minimaal één `log_implementation` (start) en één `log_test_result` (eind). --- ## Idempotency-protocol (vóór schrijven) Bij claim van een job: 1. Lees `Task.implementation_plan` — beschrijft expliciet welke files gewijzigd moeten worden. 2. Vergelijk de huidige `origin/main`-staat met die plan-instructies: - Bestaat het bestand al met de beoogde inhoud? - Bestaat de migratie al? - Bevat de relevante codepad de nieuwe symbolen/types? 3. Bij **volledige hit**: roep `log_implementation` met inhoud "Werk reeds aanwezig op origin/main vanaf commit X (Y)." Sla verify-stap over en zet `JobStatus.SKIPPED`. Task naar `DONE`. 4. Bij **gedeeltelijke hit**: log de bevindingen via `log_implementation` en doe alleen het resterende werk. Eindig met `DONE` (`ALIGNED` of `PARTIAL`) als je netto-output hebt. Dit voorkomt dubbele commits op al-gemergde branches en houdt `pushed_at` semantisch correct (alleen gevuld als er werkelijk gepusht is). --- ## Case-study: PBI-33 (5-5-2026 22:22) PBI-33 ("PLAN_CHAT — gebruikersvragen over plan") werd opnieuw aangemaakt nadat de feature al via een eerdere batch was gemerged onder cuid-style story-codes (`ST-bsjoqjnr`, `ST-p6d1odh0`, …). De worker draaide om 22:22 en zag: - **T-533** (`ST-1208` schema-werk): diff = leeg → `verify=EMPTY` → `Job.FAILED` met error "Implementatie reeds voltooid en gemerged". Volgens het nieuwe protocol had dit **`SKIPPED`** moeten zijn. - **T-534…538**: diff niet leeg op feature-branches `feat/story-7pl4dsb6` en `feat/story-0vtnydpi` (al-gemergde branches uit eerdere PR's) → `verify=DIVERGENT` → `Job.DONE` met `pushed_at=now()`. Volgens het nieuwe protocol had dit ook **`SKIPPED`** moeten zijn — branch was al closed/merged, geen nieuwe commit. - **`story_logs` voor ST-1208/1209/1210 is leeg** — geen `log_implementation`, geen `log_commit`, geen `log_test_result`. Drie protocol-overtredingen die we met deze runbook + de nieuwe `SKIPPED`-status aanpakken. --- ## Config doorgeven aan Claude Code (PBI-67) `wait_for_job` levert sinds PBI-67 een `config`-object mee in de response. Geef deze door aan `claude` als CLI-flags: ```bash claude \ --model "$MODEL" \ --permission-mode "$PERMISSION_MODE" \ --thinking-budget "$THINKING_BUDGET" \ ${MAX_TURNS:+--max-turns $MAX_TURNS} \ ${ALLOWED_TOOLS:+--allowed-tools "$ALLOWED_TOOLS"} ``` Waar: | Variabele | Bron in response | Voorbeeld | |---|---|---| | `MODEL` | `config.model` | `claude-sonnet-4-6` | | `PERMISSION_MODE` | `config.permission_mode` | `bypassPermissions` | | `THINKING_BUDGET` | `config.thinking_budget` (0 = uit) | `12000` | | `MAX_TURNS` | `config.max_turns` (null = onbegrensd) | `15` of leeg | | `ALLOWED_TOOLS` | `config.allowed_tools.join(',')` (null = alle) | `Read,Grep,WebSearch` | Verwachte CLI-aanroep per kind (kind-defaults zonder overrides): | Kind | Model | thinking | permission_mode | max_turns | |---|---|---|---|---| | `IDEA_GRILL` | sonnet-4-6 | 12000 | plan | 15 | | `IDEA_MAKE_PLAN` | opus-4-7 | 24000 | plan | 20 | | `PLAN_CHAT` | sonnet-4-6 | 6000 | plan | 5 | | `TASK_IMPLEMENTATION` | sonnet-4-6 | 6000 | bypassPermissions | 50 | | `SPRINT_IMPLEMENTATION` | sonnet-4-6 | 6000 | bypassPermissions | (geen) | **Onbekende flag:** als de huidige Claude Code-versie een vlag niet kent, log een waarschuwing en sla 'm over — geen hard error. De server blijft jobs queuen. Volledige resolver-uitleg + override-cascade staat in [job-model-selection.md](./job-model-selection.md). --- ## Referenties - Enum: `prisma/schema.prisma` → `enum ClaudeJobStatus` - Mapping: `lib/job-status.ts` (DB↔API) en `components/shared/job-status.ts` (label + kleur) - Status-data-cleanup: `app/api/cron/cleanup-agent-artifacts/route.ts` - KPI-aggregatie: `lib/insights/agent-throughput.ts` (terminal_7d inclusief SKIPPED) - Gerelateerd plan: `docs/plans/auto-pr-deploy-sync.md` Deel D - PBI-67 resolver: `scrum4me-mcp/src/lib/job-config.ts` + `lib/job-config.ts` (Sync-tab toont per-Story job-status incl. SKIPPED)