Skip to content

fix(jetbrains): resolve api key env var when selecting custom provider models - #13586

Merged
kirillk merged 8 commits into
mainfrom
mellow-grove
Aug 31, 2026
Merged

fix(jetbrains): resolve api key env var when selecting custom provider models#13586
kirillk merged 8 commits into
mainfrom
mellow-grove

Conversation

@kirillk

@kirillk kirillk commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Issue

Fixes #

Context

User feedback: a custom OpenAI-compatible provider configured with an "API key environment variable" works for chatting (the CLI resolves the env var at runtime), but Select models silently fails unless the API key is retyped into the dialog every time. The API key field is also blank when reopening the provider editor, which reads as data loss.

Tracing the model-fetch path found the root cause: the JetBrains plugin's "Select models" action never looked at the env var field at all, and had no way to. Digging further surfaced three related defects with the same root cause — env-configured custom providers are second-class in this settings UI.

Implementation

Credential resolution for model fetching now happens backend-side, where the environment that matters actually lives (the CLI process), not in the frontend dialog. In split mode the frontend can run on a different machine than the backend, so resolving in the dialog would resolve against the wrong environment.

  • CustomModelFetchDto gained directory, providerId, and env (the variable name, never a value). CustomModelFetchResultDto gained envMissing.
  • KiloBackendProviderSettingsManager.fetch() resolves the credential with precedence: typed API key → env var value via EnvironmentUtil → the provider's already-stored key from state(directory). The last fallback also removes the retyping requirement for providers authenticated with a plain stored API key, not just an env var.
  • An unresolvable env var does not block the fetch — local providers (Ollama, LM Studio, etc.) need no credential. The request still goes out; the result carries envMissing = true so the dialog can append a hint naming the variable when the fetch fails or returns no models.
  • The API key field still shows blank when editing (secrets are never prefilled, displayed, or logged). A new hint label explains that a saved key is kept when the field is left blank.

Three adjacent defects fixed for the same reason (all in KiloBackendProviderSettingsManager/KiloCliDataParser):

  • Saved custom headers were never sent when fetching models — the dialog now forwards headers loaded from the existing config.
  • Deleting a custom provider whose env var resolves was blocked, because the disconnect() guard rejected any provider with source == "env" before checking whether it was config-backed. The guard now only blocks when there's no config entry to delete.
  • Clearing the env var field left the old value in kilo.json, because the config patch omitted the key instead of setting it to null (config updates are a JSON merge-patch, so omission preserves the old value).

All changes are confined to packages/kilo-jetbrains/ (Kilo-owned; no kilocode_change markers needed, no shared packages/opencode/ files touched).

Screenshots / Video

N/A — no visual layout change beyond a new one-line hint label under the API key field in edit mode.

How to Test

Manual/local verification

  • Agent-run: ./gradlew typecheck from packages/kilo-jetbrains/ — clean, no new errors.
  • Agent-run: ./gradlew :backend:test :frontend:test from packages/kilo-jetbrains/ — 4178 tests, 0 failures, 0 errors, including 12 new backend tests (MockWebServer-backed: env/typed-key/stored-key precedence, header forwarding, envMissing, disconnect fix, config-clear fix) and 8 new frontend tests (DTO contents sent by the dialog, header forwarding, env-hint error text, hint-label visibility).
  • Agent-run: bun run script/check-opencode-annotations.ts --worktree from repo root — confirms no shared packages/opencode/ files changed.

Reviewer test steps

  1. In JetBrains, open Kilo settings → Providers → add a custom OpenAI-compatible provider with a Base URL and "API key environment variable" set to an env var exported for the IDE process, leaving API key blank.
  2. Click Select models — models should load without typing a key.
  3. Save, reopen the provider for editing, click Select models again without typing anything — should still work (previously required retyping the key).
  4. Set the env var field to a name that isn't actually set in the IDE process's environment, click Select models — the error should name the missing variable.
  5. Delete a provider that was saved with only an env var configured — it should be removable (previously blocked with "Provider is configured by environment variables.").
  6. Edit a provider, clear the env var field, save, and check kilo.json — the env key should be gone (previously it lingered).

Blocked checks and substitute verification

  • Manual sandbox IDE verification (./gradlew runIdeSplitMode) was not run in this environment; the automated backend (MockWebServer) and frontend (fake RPC) tests above exercise the same request/response paths and guard logic that a manual run would exercise.

Checklist

  • Issue linked above, or exception explained
  • Tests/verification described
  • Screenshots/video included for visual changes, or marked N/A
  • Changeset considered for user-facing changes
  • I personally reviewed the diff and can explain the changes, including any AI-assisted work.

Get in Touch

…r models

The custom OpenAI-compatible provider dialog's "Select models" action only
read the API key text field, ignoring the "API key environment variable"
field entirely. Providers configured with an env var worked for chatting
(the CLI resolves it at runtime) but failed to list models unless the key
was retyped into the dialog.

Resolve credentials backend-side, where the env that matters actually
lives (the CLI process), with precedence: typed key -> env var value via
EnvironmentUtil -> the provider's already-stored key from provider state.
This also removes the retyping requirement for providers authenticated
with a stored API key, not just an env var. An unresolvable env var no
longer blocks the fetch, since local providers (Ollama, LM Studio, etc.)
need no credential; instead the result carries an envMissing flag so the
dialog can append a hint naming the variable.

While tracing this, three related defects surfaced from the same root
cause (env-configured custom providers being second-class in this UI):
- saved custom headers were never sent when fetching models
- deleting a custom provider whose env var resolved was blocked, because
  the disconnect guard checked provider source before the config-backed
  delete branch
- clearing the env var field left the old value in kilo.json, since the
  config patch omitted the key instead of nulling it

Fixes all four. The API key field still shows blank on edit (secrets are
never prefilled or logged); a hint now explains that a saved key is kept
when the field is left blank.
@kilo-code-bot

kilo-code-bot Bot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Files Reviewed (2 files)
  • packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/provider/KiloBackendProviderSettingsManager.kt
  • packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/provider/KiloBackendProviderSettingsManagerTest.kt
Previous Review Summaries (3 snapshots, latest commit bc07f1e)

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

Previous review (commit bc07f1e)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (2 files)
  • packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/provider/KiloBackendProviderSettingsManager.kt
  • packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/provider/KiloBackendProviderSettingsManagerTest.kt

Previous review (commit a4956d3)

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/provider/KiloBackendProviderSettingsManager.kt 157 Unconditional provider delete is not a no-op; it wipes the whole entry (headers, extra fields) and has no rollback if recreate fails
Files Reviewed (3 files)
  • packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/cli/KiloCliDataParser.kt - 0 issues
  • packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/provider/KiloBackendProviderSettingsManager.kt - 1 issue
  • packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/provider/KiloBackendProviderSettingsManagerTest.kt - 0 issues

Fix these issues in Kilo Cloud

Previous review (commit ad5cf21)

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/cli/KiloCliDataParser.kt 1090 "env": null is rejected by ConfigProviderV1.Info (env is optional array, not NullOr), so custom-provider saves without an env var can 400 against the real CLI
Files Reviewed (9 files)
  • .changeset/jetbrains-custom-provider-env-var-models.md - 0 issues
  • packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/cli/KiloCliDataParser.kt - 1 issue
  • packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/provider/KiloBackendProviderSettingsManager.kt - 0 issues
  • packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/provider/KiloBackendProviderSettingsManagerTest.kt - 0 issues
  • packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/providers/ProvidersSettingsUi.kt - 0 issues
  • packages/kilo-jetbrains/frontend/src/main/resources/messages/KiloBundle.properties - 0 issues
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/settings/providers/ProvidersSettingsUiTest.kt - 0 issues
  • packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/testing/FakeProviderRpcApi.kt - 0 issues
  • packages/kilo-jetbrains/shared/src/main/kotlin/ai/kilocode/rpc/dto/ProviderSettingsDto.kt - 0 issues

Fix these issues in Kilo Cloud


Reviewed by grok-4.6 · Input: 72.4K · Output: 10.4K · Cached: 403.6K

Review guidance: REVIEW.md from base branch main

ConfigProviderV1.Info.env is Schema.optional(Array), not NullOr, so a
"env": null patch field is rejected by the real CLI's schema-validated
PATCH /global/config payload (unlike the whole provider entry, which is
Schema.NullOr and does support null for deletion). The previous commit's
fix for defect D sent that unsupported field-level null unconditionally,
which would 400 every custom-provider save that has no env var configured
-- the common case. MockCliServer's merge simulation doesn't enforce the
schema, so the added test passed locally without catching this; the
automated PR review caught it.

Clear the env var by deleting the provider entry first (schema-supported,
and a no-op when nothing exists yet) and recreating it right after without
an "env" key, instead of trying to null the field directly.
@kirillk

kirillk commented Aug 30, 2026

Copy link
Copy Markdown
Contributor Author

Good catch — ConfigProviderV1.Info.env is Schema.optional(Array), not NullOr, so that field-level null would 400 against the real CLI (only the whole provider entry supports null-for-delete). Fixed in a4956d3 by deleting the provider entry first and recreating it without the env key, instead of nulling the field directly. Confirmed via a rewritten test that exercises the two-step patch through MockCliServer's own merge-patch simulation and checks the resulting merged state, plus a full ./gradlew :backend:test :frontend:test run (4178 tests, 0 failures).

The unconditional delete-then-recreate on every save (added in the
previous commit) wiped the whole provider entry on every save that has no
env var, which is the common case. That drops any field the recreate
patch doesn't set -- e.g. a hand-authored whitelist/blacklist -- and
leaves the entry missing if the recreate patch fails partway through.

Only take the delete-first path when the provider actually has an
existing env var to clear, narrowing both risks to that specific,
deliberate action instead of every ordinary save.
@kirillk

kirillk commented Aug 30, 2026

Copy link
Copy Markdown
Contributor Author

Addressed in bc07f1e: the delete-then-recreate now only runs when the provider actually has an existing env var to clear (checked via state() before deciding), not on every save. This narrows both risks raised — wiping fields the recreate patch doesn't set, and a missing entry if the recreate patch fails — to that one deliberate action instead of the common no-env-var save path. Added a regression test asserting no delete patch fires when there's nothing to clear (via request count to /global/config), alongside the existing test covering the actual clear. Full suite: 4179 tests, 0 failures.

kirillk and others added 5 commits August 30, 2026 15:05
Add translations for settings.providers.customEnvMissing and
settings.providers.customKeyStored (introduced in this PR) across all 18
existing locale bundles. These files had fallen out of sync with the
newer provider-settings section of the base bundle, so this only adds the
two keys this PR introduces rather than backfilling the whole section.
Clearing a provider's env var deletes and recreates the whole config entry,
because the CLI config schema only accepts null for an entire provider value.
The recreate patch is built from the save DTO, and the provider dialog has no
headers field, so any headers hand-authored in kilo.json were silently dropped
by an unrelated env var change. Carry the existing headers into the recreate
patch when the save itself sets none.
@kirillk
kirillk merged commit c004d78 into main Aug 31, 2026
23 checks passed
@kirillk
kirillk deleted the mellow-grove branch August 31, 2026 19:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants