# 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`: `677a4c1` — `UsagePillViewModelTests` 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 0–3 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:

```bash
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

```bash
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:

```bash
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 0–3 decisions already made (do not redo)

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

- `05827da5` ↔ `81e37801` — 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.
- `a76d9547` ↔ `99732497` — kept both, `99732497` merged first. Done.
- `0b2fbb48` ↔ `9e307199` — kept both, `9e307199` merges first. **This is item #1/#2 in §1.**
- `20c78c95` ↔ `a76d9547`(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.
