Skip to content

Auth: stale OS-keyring entry shadows fresh API key from TUI onboarding (no-response symptom) #593

@Hmbown

Description

@Hmbown

User report

Surfaced via Telegram by the same community user who filed #586 (macOS Sequoia 15.7.3, deepseek 0.8.9, installed via cargo install):

Love your TUI! But have some issues here. (1) the colors are off [#586], and (2) it seems even with the api input, it does not connect to the actual server (no response).

The colors are #586. This issue is for the second symptom: the user typed their API key into the TUI onboarding, but the TUI behaves as if no key is set — turns hang or fail to connect.

Hypothesised root cause

crates/secrets/src/lib.rs documents the resolution order as:

Hard rule: keyring → env → config-file. Never swap.

Meanwhile the TUI onboarding's submit_api_key (crates/tui/src/tui/app.rs:1244) calls save_api_key (crates/tui/src/config.rs:2185), which writes only to ~/.deepseek/config.toml — it does not touch the OS keyring:

pub fn save_api_key(api_key: &str) -> Result<SavedCredential> {
    // ...
    let path = save_api_key_to_config_file(trimmed)?;
    Ok(SavedCredential::ConfigFile(path))
}

So a user whose flow looks like this will silently miss the new key:

  1. At any point in the past, the user ran deepseek auth set → an API key landed in the macOS Keychain under our service id.
  2. The key later becomes invalid (rotated upstream, account changed, key is from a previous DeepSeek-TUI installation, etc.).
  3. The user runs the TUI, sees the onboarding prompt, types a fresh key.
  4. submit_api_key writes it to ~/.deepseek/config.toml.
  5. At request time, the keyring lookup hits the stale entry and short-circuits before the config file is consulted.
  6. The API call uses the old/invalid key → 401 → user-visible: "no response."

Same trap if a user installed an older DeepSeek-TUI build and then upgraded — the old keyring entry survives the upgrade.

Repro plan

  • Confirm deepseek_secrets::DefaultKeyringStore::get() resolves before ~/.deepseek/config.toml for the engine's HTTP client. Trace the actual call path from submit_api_key → engine respawn → request build.
  • Manually seed the macOS Keychain with service = "deepseek-tui-deepseek" (or whatever our service id is — check crates/secrets/src/lib.rs) and a bad API key, then run deepseek and onboard with a different (good) key. Observe whether the request uses the keyring's bad key.

Proposed fixes (in increasing scope)

  1. Make submit_api_key keyring-aware. When the user re-enters a key during onboarding (or via /config api_key), also write it to the keyring slot — overwriting any stale entry. Documented behavior already says keyring is the source of truth, so save_api_key writing only to the config file is the bug. (small)
  2. Surface conflicts. If the keyring already holds a different key when onboarding starts, detect this and either ask the user "Replace stored key?" or just announce that we're overwriting it. (medium)
  3. deepseek doctor should already check this; if it doesn't, add a check that flags "keyring has key X but config.toml has key Y; resolution will use X." Make the discrepancy visible when the user reports issues. (small/medium)

v0.8.11 acceptance criteria

  • When a user saves an API key via TUI onboarding, the value lands in both the keyring (when a keyring is available) and ~/.deepseek/config.toml. New keys take effect on the next turn without requiring a manual keychain purge.
  • When a user runs deepseek doctor, a stale-keyring-vs-config mismatch is reported as a warning with copy-paste remediation steps.
  • Existing platforms without a keyring (some Linux servers, headless CI) still work via the config-file path.
  • No regressions for users who only ever used deepseek auth set and never touched the TUI onboarding.

References

  • crates/secrets/src/lib.rs — keyring backend + the documented resolution order.
  • crates/tui/src/config.rs:2180-2193save_api_key writes only to config file.
  • crates/tui/src/tui/app.rs:1244-1259submit_api_key (TUI onboarding entry).
  • crates/tui/src/tui/ui.rs:1631-1685 — onboarding state machine + engine respawn.
  • crates/cli/src/lib.rs:578no_keyring_secrets() (the bypass used in CLI flows).

Reporter

Same community user as #586, via Telegram on 2026-05-04. Worth a follow-up comment on #586 acknowledging the keyring symptom as a separate fix once we ship.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions