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.
review_task/continue_merge on a planning parent always leaves conflicts in
the tree, and the UI auto-opened the resolver on every PlanningMergeConflict
broadcast regardless of who started the merge -- so a running Claude session
resolving a unit-merge conflict could race a human editing the same shared
checkout in a resolver window neither of them asked for.
PlanningMergeOrchestrator.StartAsync now takes an externallyDriven flag (set
by ExternalMcpService's MCP-driven review_task path, left false for the UI's
ApproveReview) that rides along on the PlanningMergeConflict broadcast. The
UI only auto-opens the resolver when it's false; otherwise it shows a
persistent banner with a manual "Open resolver" button, cleared on
PlanningMergeAborted/PlanningCompleted. A new GetActiveExternalConflictsAsync
query (checked against GitService.IsMidMergeAsync rather than the in-memory
flag alone) lets the UI resync the banner on reconnect instead of trusting a
one-shot broadcast that isn't replayed after a restart.
The childless single-task conflict path was checked and needed no change --
it only broadcasts the generic TaskUpdated, never PlanningMergeConflict.
preview_merge/preview_merge_set now report isEmpty (ahead==0, or
HandlerBaseCommit==HandlerHeadCommit for a worktree-less list-handler
task) instead of leaving an empty branch indistinguishable from a small
one. preview_merge also stops throwing for worktree-less handler tasks,
falling back to their fixed commit range. review_task's parent approve
returns emptyChildren, naming the Done children whose review range
contributed nothing before the unit merge lands. TaskRefDto/TaskDto now
expose roadblockCount so a CLAUDEDO_BLOCKED child is identifiable over
MCP, since it still reaches Done per the unified parent model.
Stamps ClaudeDo.Worker's build with its exact git SHA (SourceRevisionId ->
InformationalVersion) and exposes it via a new GetWorkerBuildInfo hub call.
For the currently selected list, the shell compares that SHA against the
list's git HEAD (GitService.IsAncestorAsync) and shows a persistent footer
banner -- never auto-clearing, never shown on an unrelated repo or when the
ancestry can't be determined -- so "verified against a merge" claims aren't
silently made against a stale process. No auto-restart; the banner just
offers the existing RestartWorkerCommand.
WaitingForChildren already counted as "changed" since it's outside Queued/Running,
so waiting on a planning parent returned immediately even though its children were
still running. The new opt-in flag (default false, unchanged behavior) keeps polling
through WaitingForChildren and only reports changed once the parent reaches
WaitingForReview or a terminal status.
Remove the redundant TaskUpdated broadcast in TaskRunner.ContinueAsync's
queue-claim path, consolidate InteractiveLaunchSpecService's seven
MCP_TOOL_TIMEOUT literals into one constant (fixing the merge-helper handoff
spec's stale 200000ms value), and surface OpenQuickClaudeSession's two
failure cases via ErrorReported/footer instead of a silent no-op, with a
less ambiguous icon.
list_tasks on a list of ~100 verbosely-described tasks could return
390k+ characters in one call, blowing past the caller's token limit.
Both tools now default to lean TaskRefDto references (no
Description/Result) and take an includeDescription flag to opt back
into the full TaskDto payload — same flag-alongside-nullable-payload
idiom already used by BatchGetTaskResult/TaskConfigResult. get_task is
unchanged.
add_task and batch_add_tasks now report up to 3 open (non-terminal)
tasks in the same list with a strongly overlapping title, so a
parallel agent can notice and mention a likely duplicate instead of
silently creating one. The task is always created regardless. Uses a
cheap normalized-word overlap heuristic (no embeddings/LLM call),
robust to German umlaut/digraph spelling variants. Breaking change:
AddTask now returns AddTaskResult { task, possibleDuplicates } instead
of a bare TaskRefDto; BatchAddTaskResult gained a PossibleDuplicates
field.
## Befund (am Code verifiziert 2026-08-06)
`AppSettingsEntity.RepoImportFolders` (`src/ClaudeDo.Data/Models/AppSettingsEntity.cs:26`, JSON-Array als TEXT) ist **nicht** Teil des `AppSettingsDto` in `src/ClaudeDo.Worker/Hub/WorkerHub.cs:32-49` und wird weder von `GetAppSettings` (~:375-393) noch von `UpdateAppSettings` (~:396-424) transportiert.
Stattdessen liest und schreibt die Ui das Feld dire
ClaudeDo-Task: 64fbe15d-ae7e-4d81-b8c1-045baa7e3c87
RegisterAutostartStep rewrote the Startup .lnk on every install/update/repair
even when it already pointed at the right worker exe. AutostartShortcut.Install
now reads the existing shortcut's target via ShortcutFactory.TryGetTarget and
skips the rewrite when it matches, reporting the skip in progress output.
Legacy service/scheduled-task cleanup stays unconditional (migration safety net).
RegisterMcpStep built the registration URL from ctx.ExternalMcpPort, which the
Update pipeline never repopulates from the existing installation, so any
update silently re-registered the wizard default (47822) even when
worker.config.json had a different port configured. InstallerWorkerConfig was
also missing external_mcp_port entirely, so the installer had no way to read
it back. Port 0 (external listener disabled) now skips registration instead
of pointing Claude at 127.0.0.1:0/mcp.
A task selected before its run started kept LatestRunSessionId null, so the
roadblock reply box and Continue stayed dead until it was re-selected.
The handoff left the Phase 1-2 tile open so its last message could be read, but
its process is gone by then and the terminal renders empty -- a dead
placeholder. It is closed on handoff now.
Also drops the WARN flood from worktree cleanup (already-unregistered worktree
and already-deleted branch are normal outcomes, not failures) and replaces the
fixed sleep in UsageGate_TransitionLogging_FiresOncePerChange with the polling
helper that already sits three lines below it in the same file.
ApproveAndMergeAsync short-circuits to Done whenever a task has no active
worktree -- which is exactly how a list-handler task works, since it commits
straight into the list working dir. The verify command was skipped for the run
that lands the most on the target branch at once; the loaded command was even
discarded at the destructuring. It now runs under the same per-repo gate as the
merge path before the task may reach Done.
The two merge entry points that did not know verify_failed reported it as
"Unknown status: verify_failed" (merge modal, dropping the command output) and
as a generic Failed (worktrees batch, claiming the merge never happened).
The interactive planning TUI never reports its claude session id back, so
planning_session_id stayed NULL and UpdatePlanningSessionIdAsync had no caller
at all -- ResumeAsync always threw "No Claude session ID captured yet".
Resume now looks the id up in the transcript Claude Code writes to
~/.claude/projects/<encoded cwd>/<sessionId>.jsonl for the planning worktree
(newest file wins; a worktree hosts exactly one session) and persists it, so
the next resume is a plain DB read. No transcript -> a clear "cannot resume"
instead of resuming the wrong session.
Der Button, der aus „Problem erkannt" ein „Problem gelöst" macht: startet eine interaktive Claude-Session, die dem Nutzer beim Einrichten hilft.
## Warum externes Terminal
Der ConPTY-Stack (`PtyTerminalSession`, `ConPtyPaneView`) liegt in `ClaudeDo.Ui` und ist Avalonia — der Installer ist WPF und referenziert nur Data/Releases/Localization. Beim Fresh Install sind `app\`/`worker\` außerdem noch n
ClaudeDo-Task: 4e196058-38a3-404f-9862-4cb0e90195da
Re-runs the environment checks against the installed configuration
(worker.config.json + detected install dir) without blocking navigation
and without auto-running on window open, only on a "Recheck" click.
Extracted the check-row rendering and check-run logic (busy state,
summary, Recheck command) out of SystemCheckPage into a shared
Checks/CheckListViewModel + Checks/CheckListView, composed by both
SystemCheckPage (wizard) and the new DiagnosePage (settings) instead
of duplicating it.
QueuePicker's raw-SQL Queued->Running claim bypasses TaskStateService.StartRunningAsync,
the only other place that broadcasts TaskUpdated on this transition, so the task-list
badge stayed on "Queued" until the run finished. Send TaskUpdated for alreadyClaimed
dispatches in TaskRunner.RunAsync/ContinueAsync to close that gap.
MaxTimeoutSeconds was 170s against runs that take tens of minutes, forcing
a dozen full-context wait rounds per long-running batch. Raise it to 900s
and raise MCP_TOOL_TIMEOUT in lockstep (ClaudeProcess + every
InteractiveLaunchSpecService launch spec) to 930000ms so the client
connection actually stays open that long instead of aborting first.
Add get_queue_state (QueueStateMcpTools): configured vs. effective
parallel-slot count (via QueueService.GetSlotCountsAsync, extracted from
the former GetEffectiveMaxParallelAsync), active slots with taskId +
startedAt including the run_task_now override slot, and queued tasks in
pick order -- so a caller can observe queue occupancy instead of inferring
it from maxParallelExecutions.
Adds a read-only get_effective_run_config(taskId) tool that reports the
model/max-turns/effort/permission-mode/agent-path/system-prompt/skills a
task will actually run with, each tagged with its source (task/list/
preset/global), plus max-turns' raw requested value and clamp status.
Extracted the model/max-turns/agent-path resolution out of
TaskRunner.ResolveConfigAsync into EffectiveRunConfigResolver so the run
path and the new reporting tool share one codepath instead of risking
drift, per docs/explore-notes/worker-task-pipeline.md's max-turns trap.
update_task, update_task_status, add_task, add_subtask, set_my_day,
abort_merge, review_task, and their batch variants now return a lean
TaskRefDto (id/listId/title/status/sortOrder/isMyDay) instead of the
full TaskDto. Those tools were re-sending the caller's own description
text back on every call, wasting a large share of session context on
list-handler-style runs. get_task/list_tasks/batch_get_tasks are
untouched and still return the full DTO.
The merge-helper ("Let Claude handle it") system prompt now calls a new
handoff_list_handler MCP tool once every surviving task is enhanced,
instead of continuing into Phases 3-5 in the same session -- avoiding
paying for Phases 0-2's dedupe/rewrite context on every polling round of
the run/review/merge phases.
The tool broadcasts HandoffRequested; Mission Control opens a second
ConPTY tile for the SAME handler task id (no new task, HandlerBaseCommit
untouched) running a fresh handoff brief that starts at Phase 3. The
original tile stays open. Adds PromptKind.MergeHelperHandoff,
InteractiveLaunchSpecService.BuildForMergeHelperHandoffAsync, and the
GetMergeHelperHandoffLaunchSpec hub method.
Navigates Welcome -> SystemCheck and asserts the Next button's CanGoNext
flips false on a blocking Error+Failed check and back to true once a
recheck comes back clean, exercising the PropertyChanged wiring between
the page and WizardViewModel rather than just the page's own BlocksNavigation.
Adds a new wizard page (positioned right after Welcome, FreshInstall only)
that auto-runs EnvironmentCheckService on entry and shows one row per check
with status icon, localized title/message, and hint+help-link on
failure/unknown. A "Recheck" button re-runs it, guarded against re-entrancy.
IInstallerPage gets a BlocksNavigation default member; WizardViewModel's
Next button now binds to CanGoNext, which the current page can veto (used
here while a check run is in flight or a blocking Error+Failed result is
present — Warnings and Unknown results never block). The summary line names
the blocking checks so a disabled Next is self-explanatory.
Wires up DI for the check pipeline (IProcessRunner, IPortOwnerResolver,
per-run ClaudeCliLookup) and adds the checks.* / installer.systemCheck.*
locale keys in en.json + de.json.
Visual appearance is NOT verified — needs a manual pass in the running
installer.
Git/GitIdentity/Port/WriteAccess and Claude CLI/Version/Auth/PermissionModeAuto
checks plus the ExecutableResolver they depend on were built in two sibling
task branches that hadn't landed on main yet. Vendored the finished files in
from those branches (same content, verified building + tests green) so the
SystemCheckPage task has something to consume.
Four IEnvironmentCheck implementations in src/ClaudeDo.Installer/Checks/:
- ClaudeCliCheck (Error) — resolves ctx.ClaudeBin via ExecutableResolver, runs
--version; failure message lists searched PATH entries + fallback dirs, flags
shim resolution (.cmd/.ps1) in Detail.
- ClaudeVersionCheck (Error) — tolerant version parsing (ignores surrounding
text), numeric System.Version comparison against a named floor constant
(2.1.220, see docs/explore-notes/installer-preflight.md §3). Unparseable or
missing CLI -> Unknown, never Failed.
- ClaudeAuthCheck (Error) — `claude auth status --json`, never sends a prompt.
- PermissionModeAutoCheck (Warning) — per the note's §2 conclusion, real
auto-mode eligibility (org/model/plan) has no cheap static signal, so this
only confirms `claude --help` still lists "auto" as a --permission-mode
choice. Kept as its own check rather than folded into ClaudeVersionCheck,
since the note explicitly separates "flag recognized" from "mode eligible"
and only the former is checkable at all.
All four share a new ClaudeCliLookup that resolves the CLI and runs
--version exactly once per install run (memoized, semaphore-guarded), so
none of the three version-dependent checks repeats that call.
Foundation prerequisites this task depends on (docs/explore-notes/
installer-preflight.md, ExecutableResolver, the IEnvironmentCheck/CheckResult/
EnvironmentCheckService abstraction, ProcessCommand, IProcessRunner) existed
only on an unmerged sibling branch, not on main. Rather than merging that
whole unreviewed branch, the specific prerequisite files were copied in
as-is (git history shows their origin). GitCheck/GitIdentityCheck/PortCheck/
WriteAccessCheck from that branch were intentionally NOT brought in — out of
scope for this task.
Deviations/decisions worth flagging:
- Added ExecutableResolver.FallbackDirectories() (public) so ClaudeCliCheck
can name the checked fallback locations in its failure message; the prior
branch kept that list private.
- Paths.cs now qualifies System.Environment explicitly. Adding the
ClaudeDo.Data.Environment namespace makes bare `Environment.X` inside any
ClaudeDo.Data.* namespace resolve to the sibling namespace instead of
System.Environment (C# prefers nested/enclosing namespace members over
usings) — this broke the build until qualified.
Not done (explicitly out of scope): no DI wiring into the wizard UI, no
XAML — matches how the prerequisite Git/Port/WriteAccess checks were also
left unwired.
Implements IEnvironmentCheck for the four checks derivable without a
Claude CLI probe:
- GitCheck (Error) - resolves git via ExecutableResolver (handles .cmd
shims), parses `git --version`.
- GitIdentityCheck (Warning) - user.name/user.email presence; Unknown
(not Failed) if git itself is missing, so it doesn't duplicate GitCheck's
failure.
- PortCheck (Warning) - loopback bind probe for SignalRPort/ExternalMcpPort;
resolves the owning process via a new NetstatPortOwnerResolver and treats
a port held by the running ClaudeDo.Worker (update/repair case) as Ok.
Both ports are configurable, hence a warning.
- WriteAccessCheck (Error) - create+delete a probe file in InstallDirectory
and ~/.todo-app (walking up to the first existing parent), not an ACL
read (ACLs lie on virtualized paths).
Process calls go through a new IProcessRunner wrapping the existing static
ProcessRunner, so checks are fakeable in tests instead of spawning real
processes.
DotnetRuntimeCheck was intentionally not added: per
docs/explore-notes/installer-preflight.md, App/Worker publish
self-contained (no preinstalled runtime needed), and the Installer's own
.NET 8 Desktop Runtime requirement is self-proving - a framework-dependent
apphost can't reach managed code at all if that runtime is missing, so a
check running from inside the process can never observe a failure.
Brings in two prerequisite commits this task builds on that hadn't reached
this branch yet: the IEnvironmentCheck/EnvironmentCheckService scaffolding
and the installer-preflight.md research note.