Files
ClaudeDo/docs/handoff-list-handler-2026-08-05.md
mika kuns 032debc780 docs(handoff): mark the 2026-08-05 list-handler run complete
All three brief tasks and the nine-child Usage Monitor unit are merged and main
verified green. Correct section 5: "exited with code 1 and no result" is a
catch-all, not a CLI crash — max_turns (sonnet presets default to 30 because
app_settings.model_presets is NULL) and the account session limit both surface
through the same message.
2026-08-05 14:28:36 +02:00

13 KiB
Raw Permalink Blame History

Handoff — List-handler run on list "Claude do", 2026-08-05

COMPLETED 2026-08-05 (follow-up session). Everything in §1 and §2 is merged and main verified green. Read §0 below before §1–§8 — §5's diagnosis turned out to be wrong and the rest is now history. Still nothing pushed.

Repo: C:\Private\ClaudeDo · List id: 5f973815-050a-4136-94f0-1506a5d4560a · Branch: main (nothing pushed)


0. What the follow-up session did (and what §5 got wrong)

All merged, main green after every step (Worker 811/811, Data 143/143, Ui 292/292, Localization 16/16, builds 0 new warnings):

Merged Task Merge commit
§1 #1 9e307199 revert_merge + merge-SHA persistence 3e7126b
§1 #2 0b2fbb48 post-merge verification gate (conflict-resolved)
§1 #3 8c1c2130 roadblock reply box 519ea5a
§2 #42 c1df5b9a Hub surface for usage 6e2158d
§2 #43 f74b44d9 Usage pill 7661129
§2 #45 06068810 gate thresholds in settings 5115cfc
§2 #44 82488d2a Usage Monitor modal 338fc39
§2 #46 9c8cffe0 docs 5872666
parent 439a4daf Usage Monitor unit approved → Done (empty unit merge; all children already Merged)

Plus one hand-fix on main: 677a4c1UsagePillViewModelTests never set Loc.Current (defaults to a key-echo localizer) and only passed because another test class happened to install a real Localizer first; #44's new tests changed the ordering and broke it on main. Classic "both branches green, main red".

§5 is wrong — "exited with code 1 and no result" is NOT (only) a CLI crash

It is a catch-all hiding at least three causes. The truth is in the run log's last NDJSON line ({"type":"result", …}terminal_reason / errors / result):

  • max_turns — what actually killed #40 and #42. app_settings.model_presets is NULL, so ModelPresets.Parse falls back to the shipping defaults, and sonnet's default is 30 turns. AppSettings.DefaultMaxTurns (100) is only the fallback for an unrecognized alias, so it never applies. Every task without an explicit maxTurns override got 30 turns. → Fix used here: set_task_config(taskId, model="sonnet", maxTurns=200) before queueing.
  • api_error + "You've hit your session limit · resets 1pm (Europe/Berlin)" — the account's 5-hour limit, which took out #43's first attempt. Nothing to fix; wait for the reset, re-queue.
  • Genuine process death — the case §5 describes.

§5's operational advice still holds and is what saved #42: never reset_failed_task on one of these; check the worktree, build/test it, then set the task Queued so the agent resumes its own session and commits. #42's worktree held a complete green implementation (773/773).

Follow-up tasks: ca6e55c0 (surface the real failure reason) is new; the turn-budget/presets side is already covered by the Idle task 2de2f008 (b0317ec7 fixed only the unknown-alias half).

Still open

  • Visual passes (nobody has looked at these in a running app): usage pill in the footer and the Mission Control header; Usage Monitor modal (gauges, tables, stale/blocked bands, dark/light); the roadblock reply box; the verify-command field in the List Settings modal. See docs/open.md.
  • ~/.todo-app/prompts/planning.md still shadows the compiled default (§7.1) — unchanged.
  • wait_for_task_change is merged but not in the running Worker, so it isn't callable over MCP until the Worker is restarted. §3's sqlite poll was used instead.
  • One rough edge in the verify gate: if the verify command fails, the worktree has already been removed and its state set Merged while the task stays WaitingForReview — re-approving is then refused. Recovery is update_task_status(..., "Done") once main is fixed.

Predecessor session ran the five-phase list handler over 11 briefed tasks and, along the way, absorbed the 9-child "Usage Monitor" unit. Phases 03 are complete for the brief. What is left is Phase 4 (review + merge) for three tasks, plus the Usage chain.


1. Do this first — three brief tasks sit in WaitingForReview

Merge in this order (the order was chosen with the user and matters):

# Task Id Note
1 Feat: Merge zurücknehmen — Merge-Commit festhalten + revert_merge 9e307199-2eca-4eb7-9057-d2c675cc57ca Migration + new tool. Merge before #2
2 Feat: Verifikations-Gate nach dem Merge 0b2fbb48-d44c-4155-8c21-d3464c0bd5c2 Depends on #1's merge-SHA persistence; both edit TaskMergeService.cs
3 Feat: Antwortfeld auf der Roadblock-Karte 8c1c213004574c4fad6beb75b84b70d7 UI + localization (en and de)

For each one:

  1. get_task_diff(taskId, stat=true), then the full diff if non-trivial. Sanity-check against the task description (they are long and precise — the acceptance criteria are the checklist).
  2. review_task(taskId, decision="approve", leaveConflictsInTree=true).
  3. On conflict: open the files under the returned repoPath, resolve keeping both sides' intent, then continue_merge(taskId). Conflicts are expected and normal here.
  4. After every merge, verify main (see §4). This is non-negotiable — see §5.

Expected conflicts: TaskMergeService.cs between #1 and #2; src/ClaudeDo.Worker/CLAUDE.md and src/ClaudeDo.Data/CLAUDE.md in nearly every merge (doc bullet lists — trivial, keep both sides' entries).

2. Then the Usage Monitor unit

Parent 439a4daf166f4ab5b0fa415693d2c80d ("Usage Monitor hinzufügen") is WaitingForChildren and has no worktree of its own. It has 9 children. Four are merged, one is in flight, four are Idle.

Child Id State
#38 Data: Usage-Gate-Schwellen + Modell-Spalte c1c999b6-b800-4b6b-a821-fbc028c15772 merged b1efcdc
#39 Worker: OAuth-Usage-Client + Poller f657e316-ad72-4f45-8036-460841fc8997 merged b126a21
#40 Worker: UsageGate 06a7cc32-6ab7-4758-98f4-bee77149b2bf merged 1ee21b5
#41 Worker: TranscriptUsageReader 840fdb98-1c0e-4219-8062-c8769233fc14 merged 334cf1e
#42 Worker: Hub-Surface für Usage c1df5b9a-b911-4fe8-aab4-5876d9d85793 re-queued, in flight — read §5 before touching
#43 UI: Usage-Pill f74b44d9-7e48-4bfe-9d89-075e194d1fc9 Idle — queue once #42 is merged
#45 UI: Gate-Schwellen im Settings-Modal 06068810-5b5c-4635-80dd-62eeba89fb8c Idle — queue once #42 is merged (parallel with #43)
#44 UI: Usage-Monitor-Modal 82488d2a-8ff7-41b8-b791-367959a8f827 Idle — needs #42 and #43 merged
#46 Docs: Usage Monitor 9c8cffe0-8f7b-401e-a4f0-33b937047082 Idle — last, after everything is merged

The chain is strictly serial and you must respect it. Every child forks from main, and each one's own description hard-requires the earlier ones. Queueing them all at once is exactly what produced the original roadblock: #40 ran, found its prerequisite types only on unmerged sibling branches, and returned Done having written zero code. So: merge a child → then queue the next → verify main → repeat.

When the last child is merged the parent surfaces for review by itself; approve it to close the unit (it has no worktree, so it approves straight to Done).

3. Cheap status polling — important

list_tasks and batch_get_tasks return full descriptions and blow the token limit on this list (list_tasks over 52 tasks = ~206,000 chars; that is literally one of the bugs this run fixed). Do not poll with them. Poll the DB read-only instead:

PYTHONIOENCODING=utf-8 python - <<'EOF'
import sqlite3
c=sqlite3.connect("file:C:/Users/mika.kuns/.todo-app/todo.db?mode=ro",uri=True)
for i,s in c.execute("select id,status from tasks"):
    print(s, i)
EOF

list_worktrees is also compact and safe. New this run: wait_for_task_change(taskIds, timeoutSeconds) is now merged and is the proper primitive — it returns as soon as any listed task leaves Queued/Running (server-clamped to 170 s). Prefer it over sleeping.

4. Verify main after every merge

dotnet build src/ClaudeDo.Worker/ClaudeDo.Worker.csproj -c Release
dotnet test  tests/ClaudeDo.Worker.Tests/ClaudeDo.Worker.Tests.csproj -c Release
dotnet test  tests/ClaudeDo.Data.Tests/ClaudeDo.Data.Tests.csproj   -c Release

For the UI/localization task (#3 above, and children #43#45) also:

dotnet build src/ClaudeDo.App/ClaudeDo.App.csproj -c Release
dotnet test  tests/ClaudeDo.Ui.Tests/ClaudeDo.Ui.Tests.csproj -c Release
dotnet test  tests/ClaudeDo.Localization.Tests/ClaudeDo.Localization.Tests.csproj -c Release

.slnx needs .NET 9 — build individual csproj files, -c Release (a running Worker locks Debug).

Baseline as of this handoff: Worker 753/753, Data 143/143, build 0 warnings.

5. The trap that cost this run the most time

Two children "failed" with "Claude exited with code 1 and no result". That is a CLI crash, not bad code. In both cases the worktree held complete work that built with 0 warnings and passed the full suite (#40: 732/732, #42: 766/766) — the run just died before the auto-commit.

  • Never reset_failed_task on such a task — it discards the worktree and destroys the work.
  • Instead: cd into the worktree, git status, build + test it. If green, set the task Queued. The worktree is preserved and the agent resumes its own session (--resume), finds its work and commits it. That is how #40 was recovered.
  • #42 is mid-recovery right now via exactly this route. If it failed again, verify its worktree (C:\Private\.claudedo-worktrees\claude-do\c1df5b9a-b911-4fe8-aab4-5876d9d85793) before doing anything destructive.

Second trap: git merges cleanly and the compiler still breaks. It happened again this run — Usage/UsageModels.cs was an add/add conflict, and src/ClaudeDo.Worker/CLAUDE.md merged "cleanly" into a file with the Usage/ folder documented twice. Always read what a clean merge produced, and always run §4.

6. Phase 03 decisions already made (do not redo)

Dedupe: four candidate pairs examined, nothing cancelled. Decisions:

  • 05827da581e37801 — kept both, and 05827da5 was re-scoped: its "lean status query" half was removed because 81e37801's wait tool covers it. 05827da5 now owns only the brief-description rendering. Both are merged.
  • a76d954799732497 — kept both, 99732497 merged first. Done.
  • 0b2fbb489e307199 — kept both, 9e307199 merges first. This is item #1/#2 in §1.
  • 20c78c95a76d9547(b) — kept both, different actors. Done.

Phase 2: all 11 tasks carry acceptance criteria, real file+line references and out-of-scope sections. Three that were one-liners were researched and rewritten after asking the user (ConPTY fix approach, maxTurns-only scope, roadblock reply-box design).

Run config: maxParallelExecutions = 3. No list config exists, so effective max turns was the global 100; it was raised to 200 per-task on the five heaviest via set_task_config. 0b2fbb48, 9e307199 and 8c1c2130 still carry that override.

7. Open follow-ups worth new tasks

  1. ~/.todo-app/prompts/planning.md shadows the planning prompt. PromptFiles.EnsureExists only writes a default when the file is absent, and that file exists (dated Jun 2). The maxTurns guidance merged in 65db1cd therefore does not reach real planning sessions until that file is updated by hand. system.md and agent.md are shadowed too. merge-helper-system.md/merge-helper-initial.md do not exist, so this run's handler prompt changes are live.
  2. MCP task DTOs expose no parent/child link. The 9-child Usage unit had to be reconstructed from sortOrder and creation timestamps. get_task/list_tasks should return parentTaskId / blockedByTaskId.
  3. Visual verification open on: the ConPTY fix (open a tile on a task whose description contains ->), and — once merged — the roadblock reply box and the verify-gate field in the list settings modal.
  4. "exited with code 1 and no result" is too common. Three runs died that way today, two with finished work. Worth investigating whether the auto-commit step can be made to survive a late CLI crash.
  5. Nothing has been pushed. main is 22 commits ahead of 8d7ba1e.

8. Rules this session operated under

  • Drive merges through the MCP tools. Never raw git merge / reset / checkout.
  • Hand-resolve only markers the tools left behind, then continue_merge. When committing by hand is unavoidable, stage explicit paths — never git add -A: the main checkout is shared with other sessions.
  • For a parent/children unit merge, pass the parent id to continue_merge / abort_merge.
  • Ask the user on anything ambiguous, risky, or destructive.
  • Never delete_task to dedupe — Cancelled keeps it visible and resettable.