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.
227 lines
13 KiB
Markdown
227 lines
13 KiB
Markdown
# 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.
|