Skip to content

fix: report why a provider failed to load instead of hiding it - #14942

Merged
kirillk merged 4 commits into
mainfrom
electric-zephyr
Oct 15, 2026
Merged

kirillk merged 4 commits into
mainfrom
electric-zephyr

Conversation

@kirillk

@kirillk kirillk commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

Issue

No existing issue. Found while diagnosing a customer log bundle where /connect showed "No results found" for kilo and the real cause was visible only in the CLI log file.

What Problem This Solves

When the Kilo gateway rejects a user's credentials, the provider silently disappears from the picker instead of reporting why.

From the customer's logs, the gateway returned 401 for an organization-scoped token:

service=model-cache providerID=kilo hasToken=true hasOrganizationId=true  auth options resolved
service=model-cache providerID=kilo error={"kind":"unauthorized","status":401} model fetch error
service=model-cache providerID=kilo count=0 models fetched and cached

Two separate defects turned that into a dead end:

  1. No reason in the contract. Provider.ListResult carried failed: string[] — provider IDs with no error kind. The TUI even documented this as the reason it had to show a generic message.
  2. No row to attach a reason to. In the /provider handler, an org-scoped catalog is removed (delete filtered.kilo), and Provider.Service.list() drops zero-model providers. validProviders is then computed with remeda pickBy, which can only filter existing keys and can never re-add one — so the failedSet.has(id) retention clause was unreachable. The response contained failed: ["kilo"] but no kilo entry in all.

The result: the server knew the provider had failed and said so, but every client had nowhere to put that fact. The TUI's renderGutter/failedDescription helpers were effectively dead code in exactly the case they were written for, VS Code collapsed failed into a single boolean it never forwarded to the webview, and the user saw "No results found".

Why This Change Was Made

The fix deliberately keeps the existing 401 behavior for org-scoped tokens. Skipping the public-catalog retry is intentional — it is pinned by kilo-gateway/test/api/models.test.ts ("never retries an organization-scoped 401 against the public catalog"), because serving public models to an org-governed user would bypass that org's model policy. This PR closes the reporting gap, not the fallback rule.

Because pickBy cannot re-add a key, the placeholder has to be an explicit insert. It is built synthetically with models: {} and takes only name from the raw catalog, never models — so the public models.dev list still cannot leak into an org context. The existing not.toContain("public/leak") assertions are the regression guard.

Changes:

  • Add a typed failures: [{ providerID, kind, status? }] array to the provider list contract, with kind covering unauthorized, unauthenticated, network, http, and schema. failed: string[] is kept unchanged for back-compat.
  • Emit a models-less placeholder in all for any failed provider missing from it, filtered through the same enabled_providers/disabled_providers gate so a disabled provider is never resurrected.
  • Prefer a real tracked failure over the synthesized unauthenticated kind, so a rejected token reports unauthorized rather than "not signed in".
  • CLI/TUI: render the specific reason in the connect dialog; the row's existing click-to-connect already serves as the re-auth action.
  • VS Code: thread failures to the webview and surface it in the provider picker, the Providers tab Kilo Gateway row, and the model picker label, offering Sign in only for unauthorized/unauthenticated (a retry button would be misleading for a network or 5xx error).
  • Add the canonical copy to all 21 webview locales.

JetBrains is intentionally not included — see Dependency.

User Impact

A user whose Kilo credentials are rejected now sees "Credentials rejected (HTTP 401). Sign in again." with a working sign-in action, instead of the provider vanishing with no explanation. Network, HTTP, and schema failures report their own cause and status code without offering a misleading sign-in action.

Nothing changes for users whose providers load normally. Clients that ignore failures keep working, and an older client still receives the unchanged failed array.

Screenshots / Video

Not yet captured — this PR has visual changes and should not be marked ready for review until they are added.

Reproducing the states requires a Kilo token that the gateway rejects while an accountId is still stored, which I cannot stage without valid org credentials. I did not want to synthesize a product screenshot. The affected surfaces are the CLI /connect dialog, the VS Code provider picker, the Providers tab Kilo Gateway row, and the model picker label.

Evidence

All commands below were executed by the agent in this worktree on the final diff.

  • bun run typecheck (packages/opencode/): passed, no errors.
  • bun test ./test/kilocode/server/provider-auth-failure.test.ts ./test/kilocode/cli/tui/component/dialog-provider.test.ts ./test/kilocode/provider-list-failed-state.test.ts ./test/kilocode/catalog-recovery.test.ts ./test/kilocode/kilo-models-401-fallback.test.ts ./test/kilocode/model-cache-org.test.ts ./test/cli/run/runtime.boot.test.ts ./test/kilocode/sessions/remote-model-catalog.test.ts ./test/server/httpapi-provider.test.ts: 87 pass, 1 pre-existing skip, 0 fail.
  • bun test tests/unit/ --dots (packages/kilo-vscode/): 6895 pass, 2 skip, 0 fail.
  • bun run check-types:webview, bun run lint, bun run knip, bun run check-kilocode-change (packages/kilo-vscode/): all passed.
  • bun run script/check-opencode-annotations.ts --worktree: passed — every shared upstream edit carries a kilocode_change marker.
  • turbo typecheck across all 29 packages: ran automatically via the pre-push hook and passed, including the JetBrains Gradle typecheck. The hook initially blocked the push on a real type error in my new test file; that is fixed in e2c83ae and the hook then passed. The hook was never bypassed.

Blocked check

  • bun test (full packages/opencode/ suite) exceeded my local 15-minute command timeout and was not run to completion. Substitute verification was the targeted 9-file run above, which covers every test touching the changed provider/catalog paths, plus the repo-wide turbo typecheck. CI will run the full suite.

Reviewer test steps

  1. With a Kilo account whose stored accountId points at an org the token is no longer valid for, run kilo and open /connect.
  2. Confirm the kilo row is present with a red ! and reads "Credentials rejected (HTTP 401) — sign in again", rather than "No results found".
  3. In VS Code, open the model picker and the Providers tab and confirm the same reason appears with a working Sign in action.
  4. Sanity check the no-leak guarantee: confirm the placeholder row lists no models.

A changeset is included (patch for @kilocode/cli and kilo-code).

Dependency

None for merging.

Follow-up: the JetBrains plugin needs the same treatment but is blocked on a CLI pin bump. It is pinned to a published CLI release (kilo.cli.pinned=true, currently 7.8.7 in packages/kilo-jetbrains/package.json), and that pin feeds both the runtime binary and the generated OpenAPI spec. Until the pin moves to a release containing this change, the plugin would never receive failures, and any test against the generated ProviderList200Response.failures would not compile. VS Code has no such constraint because it builds and bundles the CLI from monorepo source, which is why both ship together here.

When an organization-scoped Kilo token is rejected with a 401, the model
catalog comes back empty, which removes the provider from `all` in the
/provider response. `pickBy` can only filter existing keys, so the
`failedSet.has(id)` retention clause was unreachable: clients received
`failed: ["kilo"]` with no row to attach a reason to, and the picker just
showed "No results found" with the cause visible only in the logs.

Add a typed `failures` array to the provider list contract and emit a
models-less placeholder for any failed provider missing from `all`, so
the CLI, TUI, and VS Code can name the cause and offer re-authentication.
The placeholder is synthetic and never sourced from the raw catalog, so
the public models.dev model list still cannot leak into an org context,
and it respects enabled_providers/disabled_providers.
@github-actions github-actions Bot added area:cli CLI, TUI and core engine area:sdk SDK area:vscode VS Code extension community Opened by someone outside the Kilo team needs-triage Waiting for a maintainer to look labels Oct 8, 2026
The provider list contract gained a failures array, so the remote
session list_models response now includes failures: []. Update the
RemoteSender expectation to match.
@github-actions github-actions Bot removed needs-triage Waiting for a maintainer to look community Opened by someone outside the Kilo team labels Oct 12, 2026
@kirillk
kirillk marked this pull request as ready for review October 14, 2026 21:30
Comment thread packages/kilo-vscode/webview-ui/src/components/settings/ProvidersTab.tsx Outdated
@kilo-code-bot

kilo-code-bot Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

The incremental pass covers the two follow-up commits. Both previously reported issues are fixed:

  • The ProvidersTab.tsx failure reason now reads the failure inside a tracked createMemo, so a changed reason (network → unauthorized, or a new HTTP status) re-renders instead of keeping the previous string. Verified against Solid's non-keyed Show behavior — the accessor passed to the child reads props.when, so the memo tracks kiloFailure().
  • The previously uncovered branches now have tests: dialog-provider.test.ts adds no-status unauthorized and http cases, and the new provider-failure.test.ts directly exercises providerFailureMessageKey (status vs. no-status keys), providerFailure, and providerFailureNeedsSignIn. All expectations match the implementations in dialog-provider.tsx:36 and provider-model.ts:58.

No new correctness, security, performance, or memory-leak concerns were found in the changed lines.

Files Reviewed (3 files)
  • packages/kilo-vscode/tests/unit/provider-failure.test.ts
  • packages/kilo-vscode/webview-ui/src/components/settings/ProvidersTab.tsx
  • packages/opencode/test/kilocode/cli/tui/component/dialog-provider.test.ts
Previous Review Summary (commit 36c0801)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 36c0801)

Status: 2 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 1

The core contract and reporting change is sound: the /provider handler now emits a models-less placeholder so a failed provider has a row to attach a reason to, the enabled/disabled gate is preserved, and the placeholder can't leak the public catalog. Client plumbing (TUI, VS Code host + webview, context, i18n across all 21 locales, changeset) is complete and backward compatible. No memory leaks were identified in the changed paths.

Issue Details (click to expand)

WARNING

File Line Issue
packages/kilo-vscode/webview-ui/src/components/settings/ProvidersTab.tsx 215 Failure reason can display a stale value because it is read outside a reactive context

SUGGESTION

File Line Issue
packages/opencode/test/kilocode/cli/tui/component/dialog-provider.test.ts 30 No-status failure branches (and providerFailureMessageKey no-status keys) are untested
Files Reviewed (45 files)
  • .changeset/provider-failure-reason.md
  • packages/kilo-vscode/src/KiloProvider.ts
  • packages/kilo-vscode/src/shared/provider-model.ts
  • packages/kilo-vscode/tests/unit/model-selector-lifecycle.test.ts
  • packages/kilo-vscode/webview-ui/src/components/settings/ProviderSelectDialog.tsx
  • packages/kilo-vscode/webview-ui/src/components/settings/ProvidersTab.tsx - 1 issue
  • packages/kilo-vscode/webview-ui/src/components/settings/provider-catalog.ts
  • packages/kilo-vscode/webview-ui/src/components/shared/ModelSelector.tsx
  • packages/kilo-vscode/webview-ui/src/context/provider.tsx
  • packages/kilo-vscode/webview-ui/src/i18n/*.ts (21 locales)
  • packages/kilo-vscode/webview-ui/src/stories/StoryProviders.tsx
  • packages/kilo-vscode/webview-ui/src/types/messages/extension-messages.ts
  • packages/opencode/src/kilo-sessions/remote-model-catalog.ts
  • packages/opencode/src/kilocode/cli/cmd/tui/component/dialog-provider.tsx
  • packages/opencode/src/provider/provider.ts
  • packages/opencode/src/server/routes/instance/httpapi/handlers/provider.ts
  • packages/opencode/test/cli/run/runtime.boot.test.ts
  • packages/opencode/test/kilocode/cli/tui/component/dialog-provider.test.ts - 1 issue
  • packages/opencode/test/kilocode/server/provider-auth-failure.test.ts
  • packages/opencode/test/kilocode/sessions/remote-model-catalog.test.ts
  • packages/opencode/test/kilocode/sessions/remote-sender.test.ts
  • packages/sdk/js/src/v2/gen/types.gen.ts
  • packages/sdk/openapi.json
  • packages/tui/src/component/dialog-provider.tsx
  • packages/tui/src/context/sync.tsx

Fix these issues in Kilo Cloud


Reviewed by deepseek-v4.1-flash · Input: 116.4K · Output: 7.6K · Cached: 311.3K

Review guidance: REVIEW.md from base branch main

Address review feedback on the provider-failure reporting change.

- ProvidersTab: read the failure inside a createMemo so a changed reason
  re-renders instead of keeping the previous text. A non-keyed Show does
  not re-run its render function when one truthy value replaces another.
- Cover the no-status branches for unauthorized and http in the TUI
  dialog test, and add a direct unit test for providerFailureMessageKey,
  providerFailure, and providerFailureNeedsSignIn.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:cli CLI, TUI and core engine area:sdk SDK area:vscode VS Code extension

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants