The usage monitor polled the undocumented OAuth usage endpoint every 60s and earned 429s. It now polls every 5 min while any task is Running and every 15 min while idle (usage_poll_interval_active_seconds / _idle_seconds, both clamped to >= 60; the old single usage_poll_interval_seconds key is gone). A 429 comes back as UsageRateLimitedException carrying Retry-After and adds exponential backoff on top, capped at 30 min and never shorter than the normal cadence; the strike count resets on the first success. The schedule arithmetic is the pure static UsagePollSchedule.NextDelay. Since the idle cadence is slow on purpose, WorkerHub.RefreshUsage drives UsageMonitorService.RefreshNowAsync behind a Refresh now button in the Usage Monitor modal: an out-of-band poll that pushes the loop's next-due time out so no double poll follows, with a 10s cooldown so click-spam can't earn a 429. Staleness now measures against the slower (idle) interval so an idle worker isn't flagged stale just for not polling.
20 KiB
ClaudeDo — Offene Punkte
Stand: 2026-07-24. Diese Datei listet die aktiv verifizierten Findings aus der Verifikations-Session vom 2026-07-24. Die laufende Verifikations-Checkliste (Pass/Fail je Abschnitt, inkl. noch offener Abschnitte §7–§11 und Kanten) lebt in docs/verification-handoff.md. Erledigtes steht in den Commits/im Code, nicht hier.
Bugs (verifiziert 2026-07-24)
- OUTCOME-Karte rendert rohes Structured-Output-JSON:
TaskMonitorViewModel.ApplyOutcome(ClaudeDo.Ui) setzt bei Tasks ohne Roadblock-MarkerSessionOutcome = resultwörtlich (Zeile ~261). Der Worker legt intask.Resultdas rohe{"summary":…,"files_changed":[…]}ab (die lesbare Fassung steht intask_runs.resultMarkdown), also zeigt die Detail-Insel OUTCOME als JSON-Blob statt als Text. Fix: (a) UI parst ein JSON-Result und zeigtsummary, oder (b) Worker schreibtsummary/resultMarkdownstatt des JSON intask.Result. Verifiziert am Taskverif §1 diff matrix. Deckt sich mit Memoryworker_testing_findings. - Approve & Merge schluckt einen „blocked"-Merge still:
DetailsIslandViewModel.ApproveReviewAsyncreagiert nur aufresult.Status == "conflict"(öffnet den Resolver); bei"blocked"(z.B. Ziel-Working-Tree hat uncommittete getrackte Änderungen) und anderen Nicht-merged/Nicht-conflict-Status passiert nichts — kein Footer-Fehler, kein Dialog (dercatchgreift nur bei Exceptions, „blocked" ist aber ein normaler Rückgabewert mitErrorMessage). User sieht „Klick tut nichts". Doppelt verifiziert (Konflikt-Approve UND sauberer additiver Approveverif §1b, beide bei dirtymain-Checkout still). Fix: beiblocked/unerwartetem Statusresult.ErrorMessageviaShowErrorAsync/FlashFooterErrorsurfacen. Verstößt gegenfeedback_ui_error_surfacing. (Der Konflikt-Resolver + 3-Pane-Editor funktionieren, sobald der Ziel-Tree sauber ist.) - „Waiting for Improvements" für Planning-Parents (Terminologie):
taskStatus.waitingForChildren= „Waiting for Improvements" (en.json:504),agentStatus.childrendito (503),childOutcomesLabel= „IMPROVEMENTS" (191). Seit dem unified-parent-Modell giltWaitingForChildrenfür Planning und Improvement-Parents — „Improvements" ist für einen Planning-Parent falsch. Auf neutrales „Waiting for Subtasks"/„Subtasks" umstellen (en und de — Localization.Tests-Parität). Verifiziert §3. - Kind-Rows aktualisieren nach Parent-Planning-Transitionen nicht live: Parent-getriebene Änderungen propagieren nicht auf die Kind-
TaskRowViewModels ohne Listen-Reload. (a) Nach „Finalize planning" bleiben Kind-Rows auf „Draft" (IsDraft) statt „Planned" (IsPlanned) — der geänderteParentFinalizedkommt nicht über das Parent-TaskUpdated-Broadcast an. (b) Nach „Discard" bleiben die (in der DB gelöschten) Draft-Kind-Rows sichtbar, bis die Liste neu geladen wird. Gemeinsame Ursache: die Kinderliste/-zustände werden bei Parent-Transitionen nicht live neu aufgelöst. Doppelt verifiziert §3 (Finalize und Discard, 2026-07-24). - AskUser-Frage erscheint NUR in Mission Control, nicht in der Task-Detail-Insel: Der
ask_user-Banner (Frage + Antwort-Eingabe) lebt ausschließlich inMonitorPaneView(Mission Control). Die Detail-Insel (DetailsIslandView) streamt zwar den Live-Log eines laufenden Tasks, zeigt aber kein Frage-Banner — ein Nutzer, der nur die Detailansicht offen hat, sieht nicht, dass der Run auf eine Antwort wartet, und läuft nach 3 min in den Timeout-Fallback. Verifiziert §7 (User schaute Detailansicht, Frage war unsichtbar; erst in Mission Control sichtbar). Fix: Frage-Banner + Inline-Antwort auch in der Detail-Insel für den gebundenen laufenden Task surfacen (VM-Zustand liegt bereits inTaskMonitorViewModel, müsste für die Detail-Insel repliziert/geteilt werden). Von Mika bei der §7-Verifikation ausdrücklich gewünscht („vermisse diese Interaktion in der Detail-Ansicht"). §7. - „New session"-Button (Mission Control) unsichtbar — Icon.Plus ist Strich-Only:
Icon.Plus=M12 5v14M5 12h14(IslandStyles.axaml:61) ist reine Linien-Geometrie ohne Fläche; im<PathIcon>(MissionControlView.axaml:38, füllt Geometrie) rendert sie unsichtbar → der Ad-hoc-„New session"-Button erscheint leer und ist nicht auffindbar (Feature + VMOpenAdHocConPtySessionAsync+ Tests funktionieren, nur das Icon fehlt sichtbar). Fix: Icon.Plus als gefüllte Geometrie authoren ODER als gestricheltesPathrendern (Icon-Gotcha in CLAUDE.md). AndereIcon.Plus-Verwendungen mitprüfen. Verifiziert §5.
UX / Nits (verifiziert 2026-07-24)
-
„Resume planning session" ist grundsätzlich kaputt (Session-Id wird nie erfasst) + Fehler wird verschluckt: Im UnfinishedPlanning-Modal macht Resume nichts. Ursache:
PlanningSessionManager.ResumeAsync(PlanningSessionManager.cs:238) bricht mitInvalidOperationException("No Claude session ID captured yet; cannot resume.")ab, wenntask.PlanningSessionIdleer ist — und der SetterTaskRepository.UpdatePlanningSessionIdAsync(TaskRepository.cs:322) hat keinen einzigen Aufrufer im Worker, d.h.planning_session_idwird nie befüllt (verifiziert anverif §3 resume-discard:planning_session_id=NULL, Token gesetzt, Drafts angelegt). Resume kann also nie erfolgreich sein. Zusätzlich verschlucktTasksIslandViewModel.ResumePlanningSessionAsyncden ganzen Block in einem leerencatch { }(~Zeile 870) → der Button ist ein stummer No-op, kein Footer-Fehler, kein Dialog (verstößt gegenfeedback_ui_error_surfacing). Hintergrund: die wt-Planning-Session ist interaktiv; ClaudeDo erfasst die claude-Session-Id dort nicht (analog zur bewusst nicht persistierten ConPTY-Session-Id, §5). Fix-Optionen: (a) beim Planning-Start/-Lauf die claude-Session-Id erfassen und viaUpdatePlanningSessionIdAsyncpersistieren, damit Resume echt resumen kann; ODER (b) Resume entfernen/deaktivieren, wenn keine Session-Id vorliegt; in JEDEM Fall den Fehler statt des leerencatchsurfacen. Verifiziert §3. §3. -
Attachments: erster Drag&Drop der Session schlug einmalig fehl („An error occurred"): Beim allerersten Drop-to-attach einer UI-Session zeigte die
DropStatus-Zeile inline einen generischen Fehler und es wurde nichts persistiert (kein File, keine DB-Row); alle folgenden Drops derselben Session + der „Add file…"-Picker + Remove funktionierten fehlerfrei. Nicht reproduzierbar nach dem ersten Mal (App-Neustart nötig, um die „erster-Drop"-Bedingung wiederherzustellen). Kandidaten: transiente SQLite-Contention (der UI-Prozess schreibttodo.dbdirekt vianew TaskAttachmentRepository, während der Worker dieselbe DB hält) ODER ein Fehler im Drop-Stream-Pfad (IStorageFile.OpenReadAsyncim Code-Behind, außerhalb destryinAddFilesAsync). Falls es erneut auftritt:AddFilesAsync/OnDropmit robusterem Error-Logging versehen (die genaue Exception-Message fehlt, weilDropStatusnur{fileName}: {ex.Message}zeigt). Verifiziert §9 (einmalig beobachtet). §9. -
Agent-Settings-Gear weicht vom übrigen Settings-Icon ab: Der Agent-Settings-Flyout-Button (
TaskHeaderBar.axaml:68) rendert ein Unicode-Glyph⚙(<TextBlock Text="⚙">), während die Listen-Nav (ListsIslandView.axaml:56) und die Listen-Settings (TasksIslandView.axaml:47) das gefüllteIcon.Settings-PathIcon (Gear-StreamGeometry, IslandStyles.axaml:110) nutzen → optisch ein anderes Zahnrad. Angleichen: den⚙-TextBlock durch<PathIcon Data="{StaticResource Icon.Settings}" .../>ersetzen. Verifiziert §8 (User-Sichtprüfung). §8. -
Session-Skills-Tab hat keinen Empty-State: Bei 0 installierten Skills zeigt der Skills-Tab (Settings) nur eine nackte leere Fläche unter der Install-Zeile — kein erklärender Hinweis (z.B. „Noch keine Skills installiert — GitHub-URL oben einfügen"). Verifiziert §8 (frischer Zustand vor ponytail-Install). Kleiner Empty-State-Text ergänzen.
-
Diff-Viewer: reiner Rename schwach dargestellt: Rename korrekt erkannt (
UnifiedDiffParser→DiffFileStatus.Renamed, BadgeStatusCode="R"), aber der File-Tree zeigt nur den neuen Namen + „+0 −0" ohne „alt → neu"-Pfad; liest sich wie „keine Änderung". Optional: alten Pfad + „renamed"-Label anzeigen. §1. -
Header-TurnsText zeigt
0/maxfür abgeschlossene Runs:TurnsText => {Turns}/{EffectiveMaxTurns}—Turnswird beim Laden eines terminalen Tasks nicht austask_runs.turnCountrestauriert (nur live gefüllt). Kosmetisch. §1. -
Conflict-Resolver: Continue/Merge-Button klickbar trotz offener Konflikte: Gate greift funktional (merged erst wenn alle Konflikte in allen Dateien gelöst), aber der Button ist nicht disabled/gegraut → früher Klick ist ein stummer No-op. Besser: disabled bis
AllResolved, oder Hinweis „N Konflikte in M Dateien offen". §4. -
Conflict-Resolver: mehrere Konfliktdateien schlecht erkennbar: Beim 2-Datei-Konflikt schwer zu sehen, dass zwei Dateien betroffen sind (File-Switcher/Anzahl zu unauffällig). Prominentere Datei-Liste / „x von y Dateien". §4.
-
Planning-aktiver Parent zeigt weiter „Idle": Parent in
planning_phase=activehatStatus=Idle(korrekt im Modell), aber der Row-Status-Chip zeigt „Idle"; derPlanningBadgeersetzt das nicht sichtbar → liest sich wie ein normaler Idle-Task. Wunsch: klarer „Planning/Draft aktiv"-Zustand, der Idle überschreibt. (Kinder zeigen korrekt „Draft".) §3. -
Blocked-by-Kette nicht sichtbar: Nach Finalize ist die sequentielle Kette korrekt gesetzt (child[i] blocked-by child[i-1]), aber die UI stellt die Reihenfolge/Abhängigkeit nicht dar. Wunsch: Kette visualisieren (z.B. „wartet auf <Vorgänger>"), nicht nur der „waiting"-Chip nach dem Queueen. §3.
-
Dequeue-„X" fehlt auf wartenden (blockierten) Kettengliedern:
CanRemoveFromQueue = IsQueued || HasQueuedSubtasks,IsQueuedverlangt leeresblocked_by. Ein gequeuetes, aber blockiertes Kind (IsWaiting) bekommt daher kein Remove-from-queue-X — nur Parent + erstes (entsperrtes) Kind. §3. -
„Open ConPTY session" erneut = Prompt wird neu gesendet, kein Resume: Da die ConPTY-Session-Id bewusst nicht persistiert wird, startet ein erneutes „Open ConPTY session" auf demselben Task eine frische Session und sendet den Task-Prompt erneut (re-runt die Arbeit im selben Worktree) statt zu resumen. So designt, aber UX-Falle — evtl. „Resume"-Affordance oder Re-Open-Warnung. §5.
Feature-Wünsche (aus der Session)
- Conflict-Resolver: farbliches Hervorheben eingefügter Zeilen im Result-Pane (grüner „flow" der übernommenen Zeilen). §4.
- Als ClaudeDo-Tasks in Liste „Claude do" erfasst: Approve erzwingt Diff/Review vor Merge (koppelt den blocked-Merge-Silent-Fail-Fix) und interaktive Planning-Session über embedded ConPTY statt externem wt-Fenster (koppelt den Planning-Permission-Prompt).
Sichtprüfung offen (2026-07-27, Batch „Claude do"-Liste)
Alles gebaut + unit-getestet, aber nicht visuell verifiziert — Mika prüft:
- Ctrl+K fokussiert die Suche,
#ist wieder tippbar (Binding warOemQuestion=#auf DE-Layout). - Titel-Edit in der Detail-Insel persistiert (400 ms debounced, wie die Beschreibung); Row-Titel + Merge-Kontext ziehen mit.
- Spinner beim ConPTY-Start: Tile erscheint sofort mit „Sitzung wird gestartet…" und deckt Launch-Spec-Roundtrip + Spawn ab. Nicht abgedeckt: die Sekunden, die die claude-TUI danach zum ersten Frame braucht (dafür bräuchte es einen Hook auf
TerminalControl.DataReceived). Bei Launch-Fehler bleibt das Tile jetzt mit Inline-Banner stehen statt zu verschwinden. - Spinner beim Refine ersetzt den Refine-Button in der Row, solange der Run läuft.
- Interactive-Chip statt „Parked" auf Tasks mit offener ConPTY-Session; Klick öffnet Mission Control und fokussiert die Pane. Accent-Tint (bewusst dieselbe „live"-Familie wie Running, klar unterschieden vom slate-blauen Parked).
- Diff-Viewer: rechter Abstand der
+n −n-Zahlen im File-Tree. - Manual-Tasks: MANUAL-Badge, Kontextmenü-Toggle, Listen-Checkbox „Manuelle Liste"; Queue/Refine/Planning ausgeblendet, ConPTY bleibt.
- Settings → Allgemein: Tabelle „Vorgaben pro Modell" (Effort + Max. Durchläufe je haiku/sonnet/opus/fable). Ersetzt das einzelne globale „Max. Durchläufe"-Feld.
Offene Entscheidungen dazu:
- Interaktive ConPTY-Sessions bekommen
--effort, aber kein--model— die Session läuft weiter unter dem Modell aus Mikas Claude-Config, der Effort kommt aus dem Preset des Modells, das ClaudeDo für die Task auflösen würde. Falls ClaudeDo auch interaktiv das Modell erzwingen soll, ist das ein Folge-Task. AppSettings.DefaultMaxTurnsist jetzt tatsächlich verdrahtet (ModelPresets.For(..., global.DefaultMaxTurns)inTaskRunner.ResolveConfigAsync): reiner Fallback für ein Modell, das auch nachModelRegistry.TryNormalizeAliasauf keine Preset-Zeile trifft — vorher war das Feld tot (hartkodierte 30). Hat weiterhin keinen eigenen Editor mehr (nur die Preset-Tabelle pro Modell); Spalte könnte später entfallen, falls das nie zutrifft.
Beobachtung (offen — Entscheidung Mika)
--permission-mode auto+ Modellhaiku→ Writes werden denied: Kontrolliert verifiziert (CLI 2.1.207): unter dem Default-Modeautobekommt sonnet Writes auto-approved (permission_denials:[]), haiku wirddenied(permission_denials:[Write], keine Datei) — eine haiku-Task macht unterautostill nichts und landet ohne Änderung inWaitingForReview. Normalbetrieb (Default = sonnet) nicht betroffen. KEINE CLI-Regression, sondern modellabhängigesauto-Verhalten. Optionen falls es nervt: haiku aus der Auswahl nehmen, ODER Runner aufacceptEdits/bypassPermissions(modell-unabhängig). Mika: erstmal beobachten. Siehe Memoryauto_permission_haiku_footgun.
Offene Verifikation (2026-07-27)
- List handler (2026-07-27) — visual pass: the Broom button is gone from the lists footer, the context-menu item appears only on lists with a working dir, and the selection dialog has no LIST column. Plus a real-Claude smoke run of the five phases (dedupe questions, enhancements landing in task descriptions, queued execution, merges).
Offene Verifikation (2026-08-05)
-
List handler owns a task (2026-08-05) — build + unit tests all green, but not visually verified: start "Let Claude handle it" on a list, confirm exactly one new task appears in that list (
Idle, MANUAL badge, title "List handler: "), the Mission Control tile is task-based (Submit for review button present), Submit for review flips it toWaitingForReview, and the detail pane's diff/merge card shows the full range of everything the run merged to main (via the newHandlerBaseCommit/HandlerHeadCommitfallback — noWorktreeEntityis created for this task, so the diff comes fromlist.WorkingDirdirectly). -
Roadblock reply field (2026-08-05) — build + unit tests all green, but not visually verified: on a
Done(orWaitingForReview/Failed/Cancelled) task with a reported roadblock, the ROADBLOCK card shows a reply textbox + Send button under the roadblock text. Check layout/spacing against the AskUser question card it's modeled on, Enter-to-send, the disabled state + hint text when there's no session ID to resume, and that an override-slot-busy error shows up in the footer log strip (not a modal) with the typed text still in the field. Approve should go straight toDonewith no merge attempt. Design choice: the review range lives as two new nullable columns directly onTaskEntity(not a phantomWorktreeEntityrow), specifically solist_worktrees/the Worktrees overview never see it. -
Post-merge verify gate (2026-08-05) — build + unit tests all green (incl. real-process
VerifyCommandRunnerexit-code/output/timeout tests andTaskMergeServicesuccess/failure/ timeout paths via a fake runner), but not visually verified: open a list's Settings modal, confirm the new "VERIFICATION" section renders below Agent with a settable/clearableVerifyCommandfield; approve a task on a list with a failing command configured and confirm the footer/error surfacing (ShowErrorAsync) actually shows the verify failure message instead of silently looking like nothing happened. Also no real-build smoke test (a realdotnet build/dotnet testinvocation as the configured command) — only fast synthetic commands (exit N,pingfor timeout) were exercised.
Offene Verifikation (2026-08-05, Usage Monitor)
- Visueller Pass Usage-Pill (Footer und Mission-Control-Header): Text/Tooltip, Dot-Zustände normal/warn/stale/blocked, Dark/Light.
- Visueller Pass Usage-Monitor-Modal: Gauges (dynamisch aus
limits[]), Modell-/Task- Tabellen, Info-Bänder (stale/gate-blocked), 7d/30d-Presets + Custom-Range, Dark/Light. - E2E Gate: das Gate greift real, sobald ein Bucket (
five_hour/seven_day) die konfigurierte Schwelle reißt — Queue-Nachschub pausiert, laufende Runs/RunNow/ConPTY/ Planning/Prime bleiben unberührt — und die Queue nimmt nach dem Reset selbstständig wieder auf (30s-Backstop, kein persistenter Pause-Zustand). - Risiko: der Usage-Endpoint (
GET https://api.anthropic.com/api/oauth/usage) ist undokumentiert und kann sich ändern; bei Ausfall/Formatänderung ist das Gate wirkungslos (fail-open by design — kein Blocker, aber der Schutz fällt dann aus, ohne dass es auffällt).
Nachtrag 2026-08-05: 429-Fix (Poll-Kadenz + Refresh-Button)
Der 60s-Poll lief in 429s. Neu: aktivitätsabhängige Kadenz (5 Min. solange ein Task Running
ist, sonst 15 Min.), 429-Backoff mit Retry-After, und ein „Jetzt aktualisieren"-Button im
Usage-Monitor-Modal (RefreshUsage → UsageMonitorService.RefreshNowAsync, 10s-Cooldown).
Unit-Tests grün, offen:
- Visueller Pass Refresh-Button im Modal (Button + Spinner + Hinweiszeile, Dark/Light, en/de) — Teil des oben schon offenen Modal-Passes.
- E2E: über ≥20 Min. mit und ohne laufenden Task beobachten, dass keine 429s mehr im Worker-Log auftauchen und die Pill trotzdem aktuell bleibt.
- Beachten: die Pill wird jetzt erst nach 3× 15 Min. als
stalemarkiert — ein echter Endpoint-Ausfall fällt vorher nur überLastErrorauf (derIsStalesofort setzt).
Offene Verifikation (2026-08-05, Max-Turns-Ceiling)
Build + unit tests grün (ResolveMaxTurns-Klemmung, Repository-Backfill von model_presets,
Migration AddMaxTurnsCeiling gegen eine Scratch-DB angewendet), aber nicht visuell
verifiziert:
- Agent-Settings-Editor (Task und Liste): Max-Turns-Feld auf einen Wert über der Ceiling
(Default 80) setzen, Hinweistext unter dem
NumericUpDownerscheint ("Runs are capped at {N} turns…"). - Settings → Allgemein → Vorgaben pro Modell: eine Zeile über 80 setzen, derselbe Hinweistext erscheint unter der Zeile.
MaxTurnsCeilingselbst hat noch keinen eigenen Editor in der Settings-UI — der Wert wird beim Laden übernommen und beim Speichern nur unverändert zurückgeschrieben (kein Clobber), aber nicht editierbar. Falls gewünscht, ein eigenes Feld ergänzen.
Bewusst verworfen (nicht erneut vorschlagen)
- CI-Build/Test-Pipeline — push-to-main + release-on-push deckt das ab; Tests laufen am Ende jeder Session.
- Real-
claude-Smoke-Test als xUnit-Test — kein Claude indotnet test; bleibt manueller Check. Tests nutzenFakeClaudeProcess. architecture.md/ ADRs — die per-Projekt-CLAUDE.md-Dateien sind die lebende Doku.- Task-Mailbox-Integration — geparkt; das generische
mcp__mailbox__*-Plugin reicht (mailbox-proposal.md). - Tag-Negation, Tag-Multi-Select, Notes-
lists.kind-Switch, Install-Service-Skript — durch die aktuelle Architektur überholt.