Documents the Checks/ + SystemCheckPage feature (gating rule, check list,
FreshInstall-only placement) and the ExecutableResolver/.cmd-shim wiring in
ClaudeDo.Worker's ClaudeCliPreflight/ClaudeProcess. Adds docs/explore-notes/installer-preflight.md
(bumped/corrected against the actual implementation) and links it from the
explore-notes README and root CLAUDE.md.
The underlying code lives only on unmerged task branches (06aca9b3.../40272c0b...),
not on main yet, and two follow-up features ("Claude Help Me" button, Config-mode
Diagnose section) were never implemented because they blocked on that same missing
merge. Both gaps are called out explicitly in the new docs and in docs/open.md's
verification checklist, instead of being asserted as done.
18 KiB
Installer preflight: CLI version gate & environment checks
Explore-note — verify before trusting. Distilled map of a subsystem, not authoritative. Last verified against commit
bdee731(2026-08-05). Drift check:git log --oneline bdee731..HEAD -- src/ClaudeDo.Worker/Lifecycle/ClaudeCliPreflight.cs src/ClaudeDo.Worker/ClaudeDo.Worker.csproj src/ClaudeDo.App/ClaudeDo.App.csproj .gitea/workflows/release.yml src/ClaudeDo.Installer/Checks src/ClaudeDo.Data/Environment/ExecutableResolver.csStable structure only (no line numbers). See docs/explore-notes/README.md.
Implementation status (as of 2026-08-05)
The research below (§1–5) led to an implementation, but it is not on main yet — it exists
on two unmerged task branches:
claudedo/06aca9b3afec4b939f59b627bfe21737—src/ClaudeDo.Installer/Checks/*(GitCheck,GitIdentityCheck,WriteAccessCheck,PortCheck,ClaudeCliCheck,ClaudeVersionCheck,ClaudeAuthCheck,PermissionModeAutoCheck,EnvironmentCheckService,ClaudeCliLookup) plusSystemCheckPage(the Fresh-Install wizard page hosting them) and its own copy ofsrc/ClaudeDo.Data/Environment/ExecutableResolver.cs.claudedo/40272c0bb3b14562b59c022d09c382b6— the originalExecutableResolver.cs, plus wiring it intoClaudeDo.Worker'sClaudeCliPreflightandClaudeProcess(the actual root-cause fix: both used to spawnclaudewithUseShellExecute = falseand no.cmd/.batshim resolution, so an npm-installedclaude.cmdwas invisible to the Worker even though it worked in a shell).
The two branches were authored independently and each vendored its own copy of
ExecutableResolver.cs (identical except 06aca9b3 adds a FallbackDirectories() diagnostic
helper); merging both cleanly requires picking one copy, not literally running git merge twice.
Two planned follow-up tasks — a "Claude Help Me" button and a Config-mode Diagnose section —
never got past a blocked first step, precisely because this prerequisite work wasn't on main
when they ran. See Environment Checks in src/ClaudeDo.Installer/CLAUDE.md and docs/open.md
for the current gap and the manual verification checklist.
What's confirmed as matching the research below: ClaudeVersionCheck.MinimumVersion is
2.1.220, exactly the "verified-floor, not a proven minimum" constant from §3. ClaudeAuthCheck
uses claude auth status --json exactly as recommended in §4, parsing only the loggedIn field.
PermissionModeAutoCheck is the static "is auto listed in --help" check recommended in §2 —
real org/model/plan eligibility is deliberately not checked, matching the recommendation not
to build that (a real task run surfaces a startup rejection fast enough on its own). No .NET
Desktop Runtime check was implemented as an IEnvironmentCheck (§5's registry-key detection
remains a documented-but-unbuilt option, not currently gating anything).
Pure research, no code changed. Answers the five questions from the "Root Cause
--permission-mode auto" task. Sources: the locally installed CLI (claude --version /
--help), the official docs at code.claude.com (fetched 2026-08-05), and this repo's own
csproj/workflow files.
1. Why can --permission-mode auto fail?
It is very rarely a CLI-version problem. Per the official permission-modes doc
(code.claude.com/docs/en/permission-modes#eliminate-prompts-with-auto-mode), auto mode
requires all of:
- Plan: any plan (Free/Pro/Max/Team/Enterprise) qualifies in principle.
- Organization: on Team/Enterprise, on by default; an admin can disable it org-wide via
permissions.disableAutoMode: "disable"in managed settings. When disabled this way, the CLI "rejects--permission-mode autoat startup" (verbatim from the doc) — not a runtime fallback, a hard reject. - Model: on the Anthropic API / Claude Platform on AWS — Opus 4.6+, Sonnet 4.6+, or Fable 5.
On Bedrock / Vertex / Foundry / signed-in gateway sessions — only Sonnet 5, Opus 4.7+, Fable 5.
Older models (Sonnet 4.5, Opus 4.5, Haiku, claude-3-*) are not supported on any provider.
A per-org
availableModelsrestriction that only allows an old model would silently make auto mode unavailable even on a Team/Enterprise org that hasn't toucheddisableAutoMode. - Provider opt-in (historical): on Bedrock/Vertex/Foundry/gateway, CLI v2.1.158–v2.1.206
required
CLAUDE_CODE_ENABLE_AUTO_MODE=1; v2.1.207 removed that requirement. This does not apply to a normal claude.ai / Console (Anthropic API) login — only to those four provider types.
A separate, easy-to-hit trap: defaultMode: "auto" in project or local settings
(.claude/settings.json, .claude/settings.local.json) is silently ignored since v2.1.142
— it must live in ~/.claude/settings.json (user scope). A repo that ships defaultMode: auto
in its own .claude/settings.json will start in Manual mode with no error at all. This
doesn't apply to ClaudeDo's case (it passes --permission-mode auto as an explicit CLI flag,
not via a checked-in settings file), but is worth knowing if the failure mode was "silently
starts in Manual" rather than "CLI errors out".
Quoted from the docs, verbatim: "If Claude Code reports auto mode as unavailable, one of these requirements is unmet; this is not a transient outage."
Not verifiable: which of the above actually hit the colleague — we have no diagnostic from
their machine (no claude auth status --json output, no claude --version, no org name). Do
not guess; if this recurs, capture claude auth status --json and claude --version from the
affected machine before further debugging.
2. How to reliably detect whether auto is supported
Static, cheap, always safe (no API call):
claude --version— semver string, e.g.2.1.220 (Claude Code).claude --help— the--permission-mode <mode>line lists the accepted enum. Confirmed by testing an invalid value locally:This is validated by the CLI's arg parser (Commander.js) before any network call — exits 1 immediately. So checking that$ claude -p "test" --permission-mode bogus error: option '--permission-mode <mode>' argument 'bogus' is invalid. Allowed choices are acceptEdits, auto, bypassPermissions, manual, dontAsk, plan.autois one of the listed choices is a legitimate, free, fast static check — but it only proves the flag is recognized, not that auto mode is actually usable (org/model/plan gating happens later, at session start, not at arg-parse time).claude auth status --json— cheap, local/fast, does not send a prompt. Returns:This is the answer to question 4 (see below) and also gives{ "loggedIn": true, "authMethod": "claude.ai", "apiProvider": "firstParty", "email": "...", "orgId": "...", "orgName": "...", "subscriptionType": "team" }subscriptionType/orgName— useful context but still not a direct "is auto mode eligible" answer (doesn't report the active model ordisableAutoModepolicy).
No cheap subcommand exists for full eligibility. Checked claude auto-mode --help: it only
has config (effective auto-mode rule config — allow/deny lists, not eligibility),
defaults (same, shipped defaults), critique, and reset. None report plan/model/org
eligibility. claude doctor (non-interactive) does not report auto-mode eligibility either
— it explicitly says "For a full setup checkup that can also fix issues, run /doctor in a
session"; the in-session /doctor slash command is the one the docs say proposes
defaultMode: auto when eligible, but that requires an interactive session, not a scriptable
preflight.
Dynamic (real probe) is the only way to fully confirm eligibility, and per the docs an
org-disabled or otherwise-ineligible account rejects at startup (fast, before any model
turn) — so a probe doesn't have to be a full expensive run. A minimal probe such as
claude -p "ok" --permission-mode auto --max-turns 1 --output-format json would fail fast on
ineligibility (reject at startup) but still costs one real turn + tokens on the success path,
and still requires a working prompt/response round trip on the happy path. Recommendation:
don't build this into an automated preflight; a static version+flag check plus auth status
covers the reliably-detectable ground, and a real first task run will surface an auto-mode
rejection immediately and cheaply (fails at startup, not mid-task) if it's actually unavailable.
Implemented as: PermissionModeAutoCheck (Warning) — the static flag-listed check only.
3. Minimum CLI version for the flags ClaudeDo uses
Flags used (from ClaudeArgsBuilder per src/ClaudeDo.Worker/CLAUDE.md): --permission-mode auto, --effort, --agents, --json-schema, --append-system-prompt, --output-format stream-json --verbose, --resume, and (installer) claude mcp add --transport http --scope user.
Not verifiable precisely. All eight of these are foundational, long-established flags.
The official changelog (raw.githubusercontent.com/anthropics/claude-code/main/CHANGELOG.md)
only retains roughly the last ~40 entries (oldest visible: 2.1.181); auto mode itself, and
--json-schema/--resume/mcp add --transport all clearly predate that window (the earliest
found reference is a bug fix mentioning "sessions created before v2.1.85" for --resume,
implying --resume existed well before 2.1.85). There is no accessible source that pins an
"introduced in vX" date for any of these eight flags — guessing one would violate the task's
explicit instruction not to invent a source-less root cause.
What is sourced, from the docs fetched 2026-08-05:
--json-schema+ invalid-schema handling: before v2.1.205, an invalid schema was silently ignored (returned unstructured text); v2.1.205 made it a hardError: --json-schema is not a valid JSON Schemaexit. Not a "does it exist" gate, but changes error-handling behavior ClaudeDo might currently rely on failing loudly.--output-format stream-json --verbose+system/init.capabilitiesarray requires v2.1.205+ (absent before). Not currently consumed by ClaudeDo per the Worker CLAUDE.md's stream handling description, so not a hard requirement today.system/init.mcp_server_errorsfield requires v2.1.219+. Not currently consumed by ClaudeDo.- Manual-mode label/
manualalias requires v2.1.200+ — irrelevant, ClaudeDo passesautoexplicitly, nevermanual. - Auto mode's Bedrock/Vertex/Foundry/gateway opt-in-env-var requirement was removed in v2.1.207
— irrelevant for a direct claude.ai/Console login (this machine:
apiProvider: "firstParty").
Decided constant (see below) is therefore the newest version we can positively confirm
works end-to-end on this machine (2.1.220), not a proven theoretical minimum — because no
lower true minimum is derivable from available sources without guessing.
Implemented as: ClaudeVersionCheck.MinimumVersion = new Version(2, 1, 220) (Error).
4. Detecting "CLI is logged in" without sending a prompt
claude auth status --json (confirmed working, instant, no API/model call):
{ "loggedIn": true, "authMethod": "claude.ai", "apiProvider": "firstParty",
"email": "...", "orgId": "...", "orgName": "...", "subscriptionType": "team" }
This is the CLI's own maintained answer — cheaper and more robust than parsing
.credentials.json directly (format may be internal/undocumented and is a hard-blocked file
for this task's own tooling; the docs page confirms it lives at
%USERPROFILE%\.claude\.credentials.json on Windows but say nothing about its schema being a
stable public contract). claude auth status --text is available for a human-readable variant;
--json is the default and the right one for a preflight to parse.
Implemented as: ClaudeAuthCheck (Error) — parses only the loggedIn boolean; any other
field, or a non-zero exit code, or unparseable JSON, becomes Unknown rather than Failed.
5. .NET runtimes required by the published app\ / worker\ artifacts
From .gitea/workflows/release.yml (the only build/publish pipeline in this repo) and the two
csproj files:
ClaudeDo.App(net8.0, Avalonia,WinExe) — published viadotnet publish ... -r win-x64 --self-contained true. Self-contained: bundles its own .NET 8 runtime. No .NET runtime needs to be pre-installed on the target machine for the app itself.ClaudeDo.Worker(net8.0,Microsoft.NET.Sdk.Web, ASP.NET Core) — same treatment:-r win-x64 --self-contained true. Also fully self-contained; no ASP.NET Core runtime needs to be pre-installed.ClaudeDo.Installer(net8.0-windows, WPF) is the one exception: published--self-contained false -p:PublishSingleFile=true— framework-dependent. The csproj comment explains why: "the WPF runtime pack isn't distributed for cross-compile on Linux CI, which made self-contained bundles crash on startup with AV in the apphost." The target machine must have the .NET 8 Desktop Runtime (x64) installed before runningClaudeDo.Installer.exe— this is the actual runtime-preflight gap, not the app/worker.
How to check on a target machine:
dotnet --list-runtimes— look for aMicrosoft.WindowsDesktop.App 8.0.xline (Desktop Runtime, required by the installer). Requires thedotnetCLI itself to be on PATH; not guaranteed present on a fresh machine that never installed the SDK, only the runtime — in that casedotnetmay not exist at all even though the runtime DLLs do.- Registry fallback (works even without the
dotnetCLI on PATH):HKLM\SOFTWARE\dotnet\Setup\InstalledVersions\x64\sharedfx\Microsoft.WindowsDesktop.App— each installed version is a subkey/value here. This is the standard documented detection mechanism for .NET Desktop Runtime presence on Windows and is what most installer-detection tooling (e.g. Squirrel, WiX bundles) uses instead of shelling out todotnet. - Not independently verified in this task: the exact registry key shape wasn't inspected
live (would require reading
HKLM\SOFTWARE\dotnet\...on this machine, which is standard .NET installer-detection convention, but out of scope to screenshot/dump here since the task is docs-only and this is a well-documented, non-project-specific Windows convention). Not implemented as anIEnvironmentCheck— none of the shipped checks verify the Desktop Runtime; if the Installer itself is running at all, .NET 8 Desktop Runtime is implicitly present (framework-dependent publish would otherwise fail to launch).
Beschlossene Konstanten
| Constant | Value | Confidence |
|---|---|---|
| Minimum CLI version | 2.1.220 |
Verified-floor, not a proven minimum. This is the newest version confirmed installed and working end-to-end on a dev machine for every flag ClaudeDo uses. No lower true minimum could be sourced (see §3) — treat any lower value as a guess. |
| Credentials file path | %USERPROFILE%\.claude\.credentials.json (Windows) |
Sourced from official docs; content/schema not inspected (hard-blocked secrets file). |
| Login-check command | claude auth status --json |
Verified locally, instant, no model call. Fields: loggedIn, authMethod, apiProvider, email, orgId, orgName, subscriptionType. |
| App/Worker runtime requirement | None — self-contained win-x64 publish | Sourced from .gitea/workflows/release.yml. |
| Installer runtime requirement | .NET 8 Desktop Runtime (x64) must be pre-installed | Sourced from .gitea/workflows/release.yml comment + ClaudeDo.Installer.csproj. |
Erkennungsstrategie pro Check
| Check | Type | Command | Expected pass output | Implemented as |
|---|---|---|---|---|
| git present + version | static | git --version (via ExecutableResolver) |
resolves, exit 0 | GitCheck (Error) |
| git identity set | static | git config --get user.name / user.email |
both non-empty | GitIdentityCheck (Warning) |
| install dir + data dir writable | static | probe-file write/delete | succeeds | WriteAccessCheck (Error) |
| SignalR/ExternalMcp ports free | static | TcpListener bind probe + owning-process lookup |
free, or owned by running ClaudeDo.Worker |
PortCheck (Warning) |
| CLI present + version | static | claude --version |
X.Y.Z (Claude Code); parse and compare X.Y.Z >= 2.1.220 |
ClaudeCliCheck (Error) / ClaudeVersionCheck (Error) |
auto recognized as a flag value |
static | claude --help (or trigger the parse error path) |
--permission-mode <mode> help text lists auto among the choices |
PermissionModeAutoCheck (Warning) |
| CLI logged in | static/cheap | claude auth status --json |
exit 0, loggedIn: true |
ClaudeAuthCheck (Error) |
| Auto mode actually eligible (org/model/plan) | not statically detectable | none exists | N/A — see §2; don't build this, let a real task run surface a fast startup rejection instead | not implemented (by design) |
| .NET Desktop Runtime present (installer only) | static | dotnet --list-runtimes (if dotnet on PATH) or registry HKLM\SOFTWARE\dotnet\Setup\InstalledVersions\x64\sharedfx\Microsoft.WindowsDesktop.App |
a Microsoft.WindowsDesktop.App 8.0.x entry exists |
not implemented (see §5) |
| App/Worker runtime present | not needed | — | self-contained, nothing to check | not implemented (not needed) |
Not verifiable (explicit)
- The actual root cause on the colleague's machine — no diagnostic data was captured from it.
- Exact stderr/exit-code wording the CLI prints when auto mode is rejected at startup for an
ineligible org/model (docs state the behavior — "rejects
--permission-mode autoat startup" — but not the literal message; this machine's account is eligible, so it couldn't be reproduced locally). - A true (not just "newest confirmed") minimum SemVer for
--effort,--agents,--json-schema,--append-system-prompt,--resume,claude mcp add --transport http --scope user— all predate the retrievable changelog window. - Live inspection of the
HKLM\SOFTWARE\dotnet\Setup\InstalledVersionsregistry shape on this machine (documented Windows convention, not independently screenshotted here).