Skip to content

fix(doctor): report subscription-CLI providers by their opt-in signal - #269

Merged
tak-bro merged 2 commits into
mainfrom
fix/doctor-subscription-cli-status
Aug 15, 2026
Merged

fix(doctor): report subscription-CLI providers by their opt-in signal#269
tak-bro merged 2 commits into
mainfrom
fix/doctor-subscription-cli-status

Conversation

@tak-bro

@tak-bro tak-bro commented Aug 14, 2026

Copy link
Copy Markdown
Owner

Summary

aicommit2 doctor reported CLAUDE_CODE as Not configured even when it was set up and generating commit messages (#268). The same divergence existed for GEMINI_CLI, which the reporter left untested, and in mirrored form for COPILOT_SDK.

checkProviderHealth had no branch for the first two, so they fell through to the generic API key check. Subscription-CLI providers authenticate through their own CLI and never carry a key, so that check could never pass — they were unreachable-healthy regardless of config. Generation worked because get-available-ais.ts gates them on a configured model instead.

COPILOT_SDK had a related divergence in its skip message: doctor demanded a configured model, while the runtime activates on a model or a key or COPILOT_GITHUB_TOKEN. A user who opted in with a key or token was told nothing was configured.

That setup does not actually generate, though — the fan-out is one request per configured model (from(getModels(ai))), so an empty model list emits zero requests and the service's own default-model fallback is never consulted (countRequests returns 0). Doctor therefore reports it as a warning naming the fix, rather than healthy. Making the runtime actually default is a generation change and is deliberately not in this PR.

The root cause is that doctor re-derived provider availability rather than reading the runtime's predicate. This PR closes both symptoms and removes one of the two duplicate encodings.

Changes

src/commands/doctor.ts

  • New branch for the subscription-CLI set: no model → No models configured, model → Model configured (Model: …).
  • COPILOT_SDK's skip now gates on the runtime's hasCopilotSdkAvailable instead of the model alone, and names all three opt-in signals, since telling the user to configure a model hid the key and token paths.
  • Opted in with no model → warning Opted in but no model configured — no requests will be sent, because that is what the fan-out does. This also short-circuits before the live auth probe, which previously ran only to describe a provider that would send nothing. checkCopilotSdkPrereqs's if (!model) guard became unreachable behind the new gate, so the guard and its now-unused parameter were removed.
  • Local hasConfiguredModel dropped in favour of the shared helper.

src/commands/get-available-ais.tsgetConfiguredModels extracted, hasConfiguredModels defined on top of it, both exported. Doctor consumes them, so the model-parsing logic exists once.

src/utils/config.tsSUBSCRIPTION_CLI_SERVICES gives the set a single home, so a future member inherits the model gate rather than the API key check that caused this bug.

tests/specs/doctor.ts — four regression tests plus a providerLine helper, so an assertion cannot be satisfied by another provider's line.

docs/providers/copilot-sdk.md — the documented healthy signal now matches the default-model behaviour.

How to Test

pnpm build
SCRATCH=$(mktemp -d); mkdir -p "$SCRATCH/aicommit2"
printf '[CLAUDE_CODE]\nmodel=sonnet\n' > "$SCRATCH/aicommit2/config.ini"
XDG_CONFIG_HOME="$SCRATCH" node ./dist/cli.mjs doctor

Before: ⏭️ CLAUDE_CODE Not configured
After: ✅ CLAUDE_CODE Model configured (Model: sonnet)

Three states covered by the suite:

Config doctor line
nothing ⏭️ COPILOT_SDK Not configured (needs model, key, or COPILOT_GITHUB_TOKEN)
COPILOT_GITHUB_TOKEN only ⚠️ COPILOT_SDK Opted in but no model configured — no requests will be sent
token + COPILOT_SDK.model ⚠️ COPILOT_SDK Unsupported classic PAT … (reaches the environment check)
CLAUDE_CODE.model=sonnet ✅ CLAUDE_CODE Model configured (Model: sonnet)

pnpm test → 413 passed, 0 failed (408 on main). Every new test was confirmed to fail before the fix by stashing the source changes and rebuilding.

Risks

  • A key-only COPILOT_SDK setup with a configured model now reaches checkCopilotSdkEnvironment, which it previously exited before. That path runs copilot --version and a live auth probe, so those users see doctor take longer. Intended — reporting whether the setup actually works is what doctor is for, and token-configured setups already paid this cost. Bounded by config.timeout.
  • The model-less warning describes the fan-out, not the credentials: such a setup may well have valid auth. The message says what is wrong (no requests will be sent) rather than implying the token is bad.
  • CLAUDE_CODE / GEMINI_CLI report healthy from config alone, without probing the claude / gemini binary. A deliberate choice: execSync inherits a non-login shell PATH, so probing would produce false "CLI not found" for users whose binary comes from a shell rc — the same class of false negative this PR removes. It matches the existing idiom (API key configured, Cookie configured) and the documented contract that the CLI is checked at request time.
  • SUBSCRIPTION_CLI_SERVICES contains COPILOT_SDK, which returns from its own branch earlier, so the shared gate never sees it. The COPILOT_GITHUB_TOKEN test pins that ordering: reaching the shared gate would report it as having no models.

Alternatives considered

  • Probing the CLI binary for CLAUDE_CODE / GEMINI_CLI. Rejected for the PATH reason above. A real binary-and-auth probe is worth doing, but as its own change with its own handling of non-login shells.
  • Reusing SUBSCRIPTION_CLI_SERVICES for the runtime gate in get-available-ais.ts. Rejected: the runtime groups OLLAMA with CLAUDE_CODE/GEMINI_CLI and gives COPILOT_SDK a wider predicate, so substituting the set there would reintroduce this bug from the other side. The constant's comment says so, to stop a future reader from making that edit.
  • Leaving COPILOT_SDK alone. It was raised as out of scope first, since its gate came out of Copilot SDK provider seems to never be taken in consideration #256/Copilot SDK provider seems to still never be taken in consideration - v2 #259. Fixing it here was a deliberate call: it is the same defect class, and leaving one half in place would have left the next reader with a working example of the pattern.
  • Making the runtime default the model when COPILOT_SDK is opted in by key or token (so subscriptionCliConfigParsers.model yields [gpt-4.1]). That is arguably what Session was not created with authentication info #254 intended, and it would make the model-less setup actually generate. Rejected here because it changes generation behaviour, not a health report, and deserves its own change and its own review.

Checklist

  • pnpm test passes (413 passed, 0 failed)
  • pnpm lint passes, zero warnings
  • pnpm build passes
  • pnpm type-check unchanged — 67 pre-existing errors, none in the touched files
  • Regression tests fail without the fix (verified by stashing)
  • Docs checked for drift (docs/, README.md)
  • Under the 300-line review budget (132 insertions, 39 deletions)

Closes #268

CLAUDE_CODE and GEMINI_CLI had no branch in checkProviderHealth, so they
fell through to the generic API key check. Those providers authenticate
through their own CLI and never carry a key, which made doctor report
them as "Not configured" no matter how they were set up, while
generation worked fine from the same config.

COPILOT_SDK's skip message carried a related divergence: doctor demanded
a configured model, while the runtime activates the provider on a model,
a key, or COPILOT_GITHUB_TOKEN. A user who opted in with a key or a
token was told that nothing was configured at all.

Both cases came from doctor re-deriving availability instead of reading
the runtime's predicate. Doctor now consumes hasCopilotSdkAvailable and
getConfiguredModels directly, and SUBSCRIPTION_CLI_SERVICES gives the
set a single home so a future member inherits the model gate rather than
the API key check.

Closes #268
The previous commit resolved an unset COPILOT_SDK model to the service
default and reported the provider healthy, on the assumption that the
service's own fallback would supply it at request time. It does not: the
fan-out is one request per configured model (from(getModels(ai)) in
ai-request.manager.ts), so an empty model list emits zero requests and
the service default is never consulted. countRequests returns 0 for a
token-only setup.

Doctor was therefore calling a setup healthy that produces no
suggestions at all — the same false report as issue #268, pointed the
other way. Such a setup is now a warning that names the fix, and the
default-model resolution is gone, so doctor no longer asserts a
behaviour the runtime does not have. Making the runtime actually default
would change generation and belongs in its own change.

This also drops a live auth probe from the model-less path, which
previously ran only to describe a provider that would send nothing.
@tak-bro
tak-bro force-pushed the fix/doctor-subscription-cli-status branch from 554f114 to 864aebc Compare August 14, 2026 03:35
@tak-bro
tak-bro marked this pull request as ready for review August 14, 2026 04:12
@tak-bro
tak-bro merged commit 63b291e into main Aug 15, 2026
2 checks passed
@tak-bro
tak-bro deleted the fix/doctor-subscription-cli-status branch August 15, 2026 00:01
@tak-bro

tak-bro commented Aug 15, 2026

Copy link
Copy Markdown
Owner Author

🎉 This PR is included in version 2.11.1 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

aic2 doctor reports CLAUDE_CODE as "Not configured" when it's working fine

1 participant