Files
ClaudeDo/docs/explore-notes/installer-preflight.md
T
mika kuns d743a9d0e9 feat(installer): add Claude CLI preflight checks (found, version, login, auto-mode)
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.
2026-08-05 19:43:34 +02:00

14 KiB
Raw Blame History

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 Stable structure only (no line numbers). See docs/explore-notes/README.md.

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 auto at 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 availableModels restriction that only allows an old model would silently make auto mode unavailable even on a Team/Enterprise org that hasn't touched disableAutoMode.
  • Provider opt-in (historical): on Bedrock/Vertex/Foundry/gateway, CLI v2.1.158v2.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:
    $ claude -p "test" --permission-mode bogus
    error: option '--permission-mode <mode>' argument 'bogus' is invalid. Allowed choices are
    acceptEdits, auto, bypassPermissions, manual, dontAsk, plan.
    
    This is validated by the CLI's arg parser (Commander.js) before any network call — exits 1 immediately. So checking that auto is 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:
    { "loggedIn": true, "authMethod": "claude.ai", "apiProvider": "firstParty",
      "email": "...", "orgId": "...", "orgName": "...", "subscriptionType": "team" }
    
    This is the answer to question 4 (see below) and also gives subscriptionType/orgName — useful context but still not a direct "is auto mode eligible" answer (doesn't report the active model or disableAutoMode policy).

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.

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 hard Error: --json-schema is not a valid JSON Schema exit. 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.capabilities array 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_errors field requires v2.1.219+. Not currently consumed by ClaudeDo.
  • Manual-mode label/manual alias requires v2.1.200+ — irrelevant, ClaudeDo passes auto explicitly, never manual.
  • 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.

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.

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 via dotnet 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=trueframework-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 running ClaudeDo.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 a Microsoft.WindowsDesktop.App 8.0.x line (Desktop Runtime, required by the installer). Requires the dotnet CLI itself to be on PATH; not guaranteed present on a fresh machine that never installed the SDK, only the runtime — in that case dotnet may not exist at all even though the runtime DLLs do.
  • Registry fallback (works even without the dotnet CLI 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 to dotnet.
  • 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).

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
CLI present + version static claude --version X.Y.Z (Claude Code); parse and compare X.Y.Z >= 2.1.220
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
CLI logged in static/cheap claude auth status --json exit 0, loggedIn: true
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
.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
App/Worker runtime present not needed self-contained, nothing to check

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 auto at 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\InstalledVersions registry shape on this machine (documented Windows convention, not independently screenshotted here).