From 42a234b46e307264df498953c0d547b14e11361d Mon Sep 17 00:00:00 2001 From: mika kuns Date: Thu, 6 Aug 2026 13:33:35 +0200 Subject: [PATCH] docs(explore-notes): reflect claim-before-create ordering in worker-task-pipeline Bumps the verified-against commit to 774f9d3 (the RunNow/picker double-dispatch race fix) and documents the new claim-before-create ordering and the RunCancellationRegistry double-registration guard. --- docs/explore-notes/worker-task-pipeline.md | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/docs/explore-notes/worker-task-pipeline.md b/docs/explore-notes/worker-task-pipeline.md index a6241b0c..840f4e0a 100644 --- a/docs/explore-notes/worker-task-pipeline.md +++ b/docs/explore-notes/worker-task-pipeline.md @@ -1,6 +1,6 @@ > **Explore-note — verify before trusting.** Distilled map of a subsystem, not authoritative. -> Last verified against commit `896d4b5` (2026-07-23). -> Drift check: `git log --oneline 896d4b5..HEAD -- src/ClaudeDo.Worker` +> Last verified against commit `774f9d3` (2026-08-06). +> Drift check: `git log --oneline 774f9d3..HEAD -- src/ClaudeDo.Worker` > Stable structure only (no line numbers). See docs/explore-notes/README.md. # Worker: Task Execution Pipeline @@ -28,9 +28,11 @@ How a task moves Queued → Running → terminal, across `src/ClaudeDo.Worker` 5. **Run Preparation** — `TaskRunner.RunAsync()` (Runner/TaskRunner.cs) - Loads task, list config, subtasks, attachments from the DB. + - `StartRunningAsync()` (only if not pre-claimed): atomic claim to Running, **before any + resource is created**. A rejected claim (task already Running) bails out immediately — + no worktree, no MCP token file. Broadcasts TaskStarted. - `PrepareRunDirectoryAsync()`: worktree (via WorktreeManager) if the list has a WorkingDir, else sandbox. Generates a per-run MCP token, writes MCP config to disk. - - `StartRunningAsync()` (only if not pre-claimed): atomic Queued → Running. Broadcasts TaskStarted. 6. **Claude Execution** — `TaskRunner.RunOnceAsync()` (Runner/TaskRunner.cs) - Creates a TaskRunEntity, points the task at the run's log path. @@ -148,6 +150,15 @@ Program.cs (DI setup) - **Slot limit** — respects MaxParallelExecutions; a backstop timer wakes even if a Wake() is missed. - **Pre-claimed tasks** — the dispatcher pre-claims via the picker; the override slot (RunNow/ContinueTask) must call StartRunningAsync if a task is not pre-claimed. +- **Claim before create** — `TaskRunner.RunAsync`'s unclaimed path calls `StartRunningAsync` + *before* `PrepareRunDirectoryAsync`. RunNow racing the picker for the same Queued row used to + create the worktree first and only claim afterwards, so the losing dispatch could hit + WorktreeManager's branch-collision self-heal and force-remove the winner's live worktree + mid-run. `OverrideSlotService.RunNow` also fast-rejects a task already Running in the DB + (defense in depth; the picker's atomic SQL claim is the real arbiter either way). + `RunCancellationRegistry.Register` refuses (and logs) a second registration for the same task + id instead of silently overwriting the first, so a losing dispatch's cleanup can't unregister + the winner's CTS out from under it. - **Terminal writes** — use `CancellationToken.None`; a task is never left Running after crash/cancel. - **Per-run MCP tokens** — each run gets a unique token scoping tool access; unregistered on end. - **Auto-retry** — one automatic retry if a session exists and the first run failed.