Files
ClaudeDo/docs/open.md
T
mika kuns 2700c3d817 fix(usage): stop 429s with an activity-dependent poll cadence + manual refresh
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.
2026-08-05 16:40:34 +02:00

20 KiB
Raw Blame History

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-Marker SessionOutcome = result wörtlich (Zeile ~261). Der Worker legt in task.Result das rohe {"summary":…,"files_changed":[…]} ab (die lesbare Fassung steht in task_runs.resultMarkdown), also zeigt die Detail-Insel OUTCOME als JSON-Blob statt als Text. Fix: (a) UI parst ein JSON-Result und zeigt summary, oder (b) Worker schreibt summary/resultMarkdown statt des JSON in task.Result. Verifiziert am Task verif §1 diff matrix. Deckt sich mit Memory worker_testing_findings.
  • Approve & Merge schluckt einen „blocked"-Merge still: DetailsIslandViewModel.ApproveReviewAsync reagiert nur auf result.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 (der catch greift nur bei Exceptions, „blocked" ist aber ein normaler Rückgabewert mit ErrorMessage). User sieht „Klick tut nichts". Doppelt verifiziert (Konflikt-Approve UND sauberer additiver Approve verif §1b, beide bei dirty main-Checkout still). Fix: bei blocked/unerwartetem Status result.ErrorMessage via ShowErrorAsync/FlashFooterError surfacen. Verstößt gegen feedback_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.children dito (503), childOutcomesLabel = „IMPROVEMENTS" (191). Seit dem unified-parent-Modell gilt WaitingForChildren fü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änderte ParentFinalized kommt 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 in MonitorPaneView (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 in TaskMonitorViewModel, 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 + VM OpenAdHocConPtySessionAsync + Tests funktionieren, nur das Icon fehlt sichtbar). Fix: Icon.Plus als gefüllte Geometrie authoren ODER als gestricheltes Path rendern (Icon-Gotcha in CLAUDE.md). Andere Icon.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 mit InvalidOperationException("No Claude session ID captured yet; cannot resume.") ab, wenn task.PlanningSessionId leer ist — und der Setter TaskRepository.UpdatePlanningSessionIdAsync (TaskRepository.cs:322) hat keinen einzigen Aufrufer im Worker, d.h. planning_session_id wird nie befüllt (verifiziert an verif §3 resume-discard: planning_session_id=NULL, Token gesetzt, Drafts angelegt). Resume kann also nie erfolgreich sein. Zusätzlich verschluckt TasksIslandViewModel.ResumePlanningSessionAsync den ganzen Block in einem leeren catch { } (~Zeile 870) → der Button ist ein stummer No-op, kein Footer-Fehler, kein Dialog (verstößt gegen feedback_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 via UpdatePlanningSessionIdAsync persistieren, damit Resume echt resumen kann; ODER (b) Resume entfernen/deaktivieren, wenn keine Session-Id vorliegt; in JEDEM Fall den Fehler statt des leeren catch surfacen. 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 schreibt todo.db direkt via new TaskAttachmentRepository, während der Worker dieselbe DB hält) ODER ein Fehler im Drop-Stream-Pfad (IStorageFile.OpenReadAsync im Code-Behind, außerhalb des try in AddFilesAsync). Falls es erneut auftritt: AddFilesAsync/OnDrop mit robusterem Error-Logging versehen (die genaue Exception-Message fehlt, weil DropStatus nur {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üllte Icon.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 (UnifiedDiffParserDiffFileStatus.Renamed, Badge StatusCode="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/max für abgeschlossene Runs: TurnsText => {Turns}/{EffectiveMaxTurns}Turns wird beim Laden eines terminalen Tasks nicht aus task_runs.turnCount restauriert (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=active hat Status=Idle (korrekt im Modell), aber der Row-Status-Chip zeigt „Idle"; der PlanningBadge ersetzt 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, IsQueued verlangt leeres blocked_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 war OemQuestion = # 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.DefaultMaxTurns ist jetzt tatsächlich verdrahtet (ModelPresets.For(..., global.DefaultMaxTurns) in TaskRunner.ResolveConfigAsync): reiner Fallback für ein Modell, das auch nach ModelRegistry.TryNormalizeAlias auf 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 + Modell haiku → Writes werden denied: Kontrolliert verifiziert (CLI 2.1.207): unter dem Default-Mode auto bekommt sonnet Writes auto-approved (permission_denials:[]), haiku wird denied (permission_denials:[Write], keine Datei) — eine haiku-Task macht unter auto still nichts und landet ohne Änderung in WaitingForReview. Normalbetrieb (Default = sonnet) nicht betroffen. KEINE CLI-Regression, sondern modellabhängiges auto-Verhalten. Optionen falls es nervt: haiku aus der Auswahl nehmen, ODER Runner auf acceptEdits/bypassPermissions (modell-unabhängig). Mika: erstmal beobachten. Siehe Memory auto_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 to WaitingForReview, and the detail pane's diff/merge card shows the full range of everything the run merged to main (via the new HandlerBaseCommit/HandlerHeadCommit fallback — no WorktreeEntity is created for this task, so the diff comes from list.WorkingDir directly).

  • Roadblock reply field (2026-08-05) — build + unit tests all green, but not visually verified: on a Done (or WaitingForReview/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 to Done with no merge attempt. Design choice: the review range lives as two new nullable columns directly on TaskEntity (not a phantom WorktreeEntity row), specifically so list_worktrees/the Worktrees overview never see it.

  • Post-merge verify gate (2026-08-05) — build + unit tests all green (incl. real-process VerifyCommandRunner exit-code/output/timeout tests and TaskMergeService success/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/clearable VerifyCommand field; 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 real dotnet build/ dotnet test invocation as the configured command) — only fast synthetic commands (exit N, ping for 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 (RefreshUsageUsageMonitorService.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 stale markiert — ein echter Endpoint-Ausfall fällt vorher nur über LastError auf (der IsStale sofort 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 NumericUpDown erscheint ("Runs are capped at {N} turns…").
  • Settings → Allgemein → Vorgaben pro Modell: eine Zeile über 80 setzen, derselbe Hinweistext erscheint unter der Zeile.
  • MaxTurnsCeiling selbst 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 in dotnet test; bleibt manueller Check. Tests nutzen FakeClaudeProcess.
  • 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.