get_task/batch_get_tasks now return failureReason (max_turns|timeout|error|
cancelled|unknown) plus failureTurnsUsed/failureMaxTurns on a Failed task, so
max_turns (worktree usually fine, continue_task) is distinguishable from a
real error (reset_failed_task) without pulling get_task_log's raw NDJSON.
Classified and stamped onto TaskEntity by TaskRunner.MarkFailed via
TaskStateService.FailAsync; TaskRunEntity also keeps the CLI's raw
terminal_reason/result_subtype/errors for deeper diagnosis. reset_failed_task's
description now warns explicitly that it discards the worktree and points at
continue_task for max_turns. Surfaced on the task card's status-chip tooltip.
TaskStateService.CancelAsync allowed cancelling a WaitingForReview task
even while PlanningMergeOrchestrator was mid-drain on it: ApproveReview
awaits the whole multi-subtask merge synchronously, so a concurrent
CancelReview (UI or MCP) could flip the parent to Cancelled while the
orchestrator kept merging children onto the target branch, then
FinalizeParentDoneAsync would find the parent no longer WaitingForReview
and give up - leaving the merged diffs stranded with no rollback.
CancelAsync now rejects with a clear reason when HasActiveMerge(taskId)
is true. TaskStateService can't take PlanningMergeOrchestrator as a
direct constructor dependency (circular back to ITaskStateService), so
it takes a lazily-resolved Func<IActiveMergeState> instead, mirroring
the existing Func<ITaskStateService> cycle-break already used for
PlanningChainCoordinator.
UI polish: DetailsIslandViewModel.IsMergeDraining gates
CancelReviewCommand's CanExecute (same shape as
WorktreesOverviewModalViewModel.IsMerging), and the command's catch now
raises ErrorReported instead of swallowing the rejection silently.
Deleting a child from the details pane hard-deleted straight from the UI
process via TaskRepository, bypassing TaskStateService.TryAdvanceParentAsync
entirely. Deleting the last child of a WaitingForChildren parent that way
left it wedged forever. WorkerHub.DeleteTask now mirrors the MCP delete_task
tool (running-task guard, FK-friendly error, advance-parent call), and the
UI goes through it.
TryAdvanceParentAsync also short-circuited when zero children remained,
treating "no children left" as "nothing to evaluate" instead of "all done" -
removed the early return so an empty child list (vacuously) counts as all
terminal.
RecoverStaleRunningAsync bulk-flipped Running rows to Failed via raw
ExecuteUpdate, skipping the chain/parent side effects every other terminal
transition triggers. After a worker crash mid-run of a planning/improvement
child, the chain successor's BlockedByTaskId was never cleared and a
WaitingForChildren parent could wedge forever with no event left to
re-check it. Now each recovered task runs the same OnChildTerminalAsync
side effects (chain advance + parent advance) as FailAsync, best-effort.
External tasks finished outside a ClaudeDo run had no way to close out
their tracking task; update_task_status now permits Done alongside
Idle/Queued/Cancelled, refusing it when the task has an active
worktree so review_task stays the only path that merges.
Reminders written down as todos had no home: every task looked like Claude work.
A manual task now shows a MANUAL badge and hides send-to-queue, refine and the
planning session; the queue picker, daily prep and the list handler all skip it,
with a TaskStateService guard so the MCP surface and hub cannot start one either.
Opening a hand-driven ConPTY session stays available on purpose.
A list can be marked manual in its settings, which makes tasks created there
(UI and MCP add_task) start out manual. Toggle per task from its context menu.
An embedded ConPTY session leaves its worktree changed but never touches
task status, so hand-driven work had no path into the review/merge flow.
Add SubmitTaskForReview: commit the worktree (same auto-commit as a headless
run), then transition Idle/Failed -> WaitingForReview via the new
TaskStateService.SubmitInteractiveForReviewAsync. Approve then merges it.
Surfaces: a 'Submit for review' button in the detail work console (shown for
an Idle/Failed task with a worktree) and on the ConPTY Command Center pane
header (task-based panes; closes the pane on success). Tests cover the new
transition (Idle/Failed accepted, Running/Queued/Done/Review rejected).
- CancelAsync now signals the running Claude process of the cancelled task and
its cascaded children via the new RunCancellationRegistry (queue + override
slots register their CTS there) instead of only flipping DB state.
- external MCP review_task 'approve' now mirrors the hub's ApproveReview:
unit merge for parents, ApproveAndMergeAsync for childless tasks, optional
targetBranch; ReviewTaskResult carries mergeStatus/conflicts.
Confirmed via docs/open.md (Korrektheits-Review 2026-06-09): TaskStateService.CancelAsync
only flipped the target row to Cancelled - a Queued/Running child kept going and could
still commit into its worktree after the parent was gone.
CancelAsync now also cancels the task's own non-terminal children (Idle/Queued/Running/
WaitingForReview/WaitingForChildren) in the same pass, clearing BlockedByTaskId so no
successor is left wedged, and broadcasts TaskUpdated for each.
DeleteTask never re-evaluated a WaitingForChildren parent, so deleting
the last non-terminal child left it stuck (the BlockedByTaskId SET NULL
FK only repairs the blocked chain, not parent status). Expose
TaskStateService.TryAdvanceParentAsync on the interface and call it
from ExternalMcpService.DeleteTask after a child delete.
OverrideSlotService dispatches RunAsync before calling StartRunningAsync,
so a preflight failure (list not found, worktree setup) can reach MarkFailed
while the task is still Queued. The guard is intentional, not dead code.
- Add comment in FailAsync explaining the OverrideSlotService preflight gap
- Add FailAsync_FromQueued_TransitionsToFailed test
- Update CLAUDE.md transition table with the precise rationale
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces the direct EF Status write in PlanningMergeOrchestrator with
_state.ApproveReviewAsync, enforcing the TaskStateService invariant as
sole owner of Status writes. Handles the improvement-parent path where
TaskMergeService already approved the parent's own worktree during the
drain (status == Done on entry → still success). If the parent was
concurrently cancelled, the transition guard rejects the approve,
PlanningCompleted is not broadcast, and the cancelled status is
preserved. ApproveReviewAsync now also sets FinishedAt.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add WaitingForChildren to the CancelAsync guard so a parent waiting on its
children can be cancelled.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A finalized planning parent now joins the unified parent lifecycle:
WaitingForChildren while its child chain runs (or WaitingForReview directly
if it has no children), advancing to review like an improvement parent.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Collapse TryCompleteParentAsync (planning -> Done) and
TryAdvanceImprovementParentAsync (improvement -> WaitingForReview) into one
TryAdvanceParentAsync that surfaces any WaitingForChildren parent for review
once all children are terminal. Planning parents no longer auto-complete.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1. ArgumentList (fix injection): ClaudeArgsBuilder.Build() now returns
IReadOnlyList<string>; ClaudeProcess populates ProcessStartInfo.ArgumentList
instead of Arguments, so values like system prompts are never shell-split.
DailyPrepPrompt, RefinePrompt, and WeekReportService migrated similarly.
All IClaudeProcess fakes updated.
2. ContinueAsync exception guard: wrap RunOnceAsync in try/catch matching
the RunAsync pattern so an unexpected exception never leaves the task
stuck in Running status.
3. Planning chain cascade: OnChildFinishedAsync now calls CancelAsync on
the immediate blocked successor when a child fails or is cancelled,
triggering a recursive cascade that clears the entire remaining chain
instead of leaving it wedged.
4. FailAsync guard: restrict valid source states to Running and Queued;
WaitingForReview -> Failed is now rejected, preventing an invalid
transition that could corrupt the review workflow.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Clear ReviewFeedback only after a successful re-run so a failed/cancelled
run keeps it for a manual retry.
- Clear stale StartedAt/FinishedAt when rejecting a task back to the queue.
- Only non-planning standalone tasks gate on review (guard PlanningPhase).
- Hide "send to queue" for WaitingForReview tasks so review isn't bypassed.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Planning subtasks are now "Draft" until their parent plan is finalized,
then "Planned" (queueable). Finalizing a plan no longer auto-queues the
child chain; the user sends the plan to the queue explicitly.
- TaskStateService rejects a child entering Queued/Running unless its parent
is Finalized; this single invariant covers UI, queue, RunNow and MCP paths
- WorkerHub.SetTaskStatus routes Queued through the gated EnqueueAsync
- Finalize call sites pass queueAgentTasks: false
- PlanningChainCoordinator.QueuePlanAsync guards the chain build on Finalized
- TaskRowViewModel derives Draft/Planned from ParentFinalized; gates
CanSendToQueue / CanQueuePlan; view shows a PLANNED badge
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds ForceSetStatusAsync on ITaskStateService (no transition guards)
plus SetTaskStatus / SetTaskTags / GetAllTags hub methods so the UI
can edit a task's status and tags directly. PlanningHubTests ctor
updated for the new ITaskStateService dependency.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Slice 6 of the worker state and queue consolidation refactor.
* Drop Manual, Planning, Planned, Draft, Waiting from the TaskStatus enum
and from the EF value converter; only the lifecycle values remain
(Idle, Queued, Running, Done, Failed, Cancelled).
* Add migration RetireLegacyTaskStatus that rewrites existing rows:
manual/draft -> idle, planning -> idle+planning_phase=active,
planned -> idle+planning_phase=finalized, waiting -> queued+blocked_by
derived from sort_order via a CTE with LAG().
* Reroute every call site that compared/set legacy values to the new
three-field model (Status + PlanningPhase + BlockedByTaskId), including
the planning repo helpers, MCP services, the planning chain coordinator,
and the UI view-models. TaskRowViewModel now exposes PlanningPhase to
drive the planning badge.
* Refresh Worker/CLAUDE.md and Data/CLAUDE.md, the docs/plan.md status
section, and the planning verification notes in docs/open.md.
Slice 4 of the worker state consolidation refactor. Eliminates the
"queue never picks up planning tasks" bug structurally by routing both
the manager and MCP finalize paths through TaskStateService and
PlanningChainCoordinator.SetupChainAsync, where the auto-wake on enqueue
guarantees the queue picker claims the first child immediately.
- Delete TaskRepository.FinalizePlanningAsync; PlanningSessionManager
now orchestrates via _state.FinalizePlanningAsync + _chain.SetupChainAsync.
- Rename QueueSubtasksSequentiallyAsync to SetupChainAsync (internal);
layout is now Status=Queued + BlockedByTaskId, with auto-attached agent tag.
- OnChildFinishedAsync looks up the successor by BlockedByTaskId, drops
the legacy Waiting status lookup.
- PlanningMcpService.Finalize routes through state+chain; EditableStatuses
drops Waiting and adds Idle; gate uses PlanningPhase==Active.
- TaskStateService.FinalizePlanningAsync clears the planning session token.
- UI: TaskRowViewModel adds BlockedByTaskId; IsQueued/IsWaiting reflect
the new layout; TasksIslandViewModel.RemoveFromQueueAsync clears
BlockedByTaskId on dequeue.
- New regression test PlanningEndToEndTests.FinalizeAsync_FirstChildIs
ClaimedByPicker_WithinDeadline asserts the picker claims the first
child within 200ms with no manual WakeQueue.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Slice 3 of the worker state and queue consolidation refactor.
- Add IQueueWaker / QueueWaker (singleton holding the wake semaphore).
- Add IQueuePicker / QueuePicker; raw SQL UPDATE...RETURNING moves out of
TaskRepository.GetNextQueuedAgentTaskAsync (deleted) and now also filters
on blocked_by_task_id IS NULL and writes started_at on claim.
- TaskStateService takes IQueueWaker directly; the Func<QueueService>
indirection is gone. State transitions to Queued auto-wake the dispatcher.
- QueueService waits via the shared waker and dispatches via the picker.
- Drop explicit _queue.WakeQueue() calls in WorkerHub.QueuePlanningSubtasksAsync
and ExternalMcpService.AddTask. The hub WakeQueue endpoint stays for
diagnostics, delegating to _waker.Wake().
- Migrate tests; pre-existing flaky AppSettings/ExternalMcp tests untouched.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Slice 2 of the worker state consolidation refactor (spec sections 2 and 8).
Adds Worker/State/ITaskStateService + TaskStateService as the single component
that mutates Status, PlanningPhase, and BlockedByTaskId. Each transition is one
atomic ExecuteUpdate with a WHERE filter on the expected source status, so
parallel claims are TOCTOU-free. Side effects (queue wake on -> Queued, hub
TaskUpdated broadcast, chain advance + parent completion on terminal child)
are owned by the service so callers no longer need to remember them.
Migrated callers (mechanical, behavior preserved):
- TaskRunner: HandleSuccess/HandleFailure/MarkFailed/RunAsync/ContinueAsync
- StaleTaskRecovery: bulk recover stale Running tasks
- TaskResetService: status flip (worktree cleanup stays in service)
- PlanningSessionManager.StartAsync: status flip via state, token write via repo
- PlanningChainCoordinator.OnChildFinishedAsync: routes the next-sibling write
through state.UnblockAsync (Slice 4 finishes the rewrite)
- ExternalMcpService.UpdateTaskStatus: Queued case via state.EnqueueAsync
Repo Mark*Async helpers (MarkRunning/MarkDone/MarkFailed/FlipAllRunningToFailed)
are now internal; ClaudeDo.Data grants InternalsVisibleTo to ClaudeDo.Worker
and ClaudeDo.Worker.Tests for the existing repo-level tests.
DI: TaskStateService is registered as Singleton in both the main app and the
external-MCP app; the queue-wake delegate captures sp -> QueueService.WakeQueue
to break the TaskStateService -> QueueService -> TaskRunner -> TaskStateService
construction cycle. PlanningChainCoordinator takes Func<ITaskStateService> for
the same reason; Slice 3 will replace both with IQueueWaker.
Tests: TaskStateServiceTests covers happy + reject for every transition, the
parallel StartRunningAsync claim race, child-terminal chain advancement, and
stale recovery. Existing service/repo tests are updated to construct the new
state-service via a TaskStateServiceBuilder helper. Pre-existing constructor
drift in QueueService/ExternalMcp/PlanningHub tests is patched to keep the
test project building (the surrounding test logic is otherwise untouched).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>