Research -- API Key UX Improvement
Date: 2026-02-19 Owner: agent Phase: Research
Goal
Map the current API key validation and error UX when a user selects a model without a configured key, and identify all touchpoints that need improvement to provide an empathetic, guided experience instead of a dead-end error.
Problem Statement
When a user picks a model via the model picker and lacks the API key for that provider, the app:
- Shows a transient toast notification ("Missing API key: OPENROUTER_API_KEY")
- Writes a yellow hint to
rich_log(with no config path on the picker flow) - Silently refuses to switch the model
- Closes the picker screens, leaving the user stranded on the old model with no recovery path
No inline key entry is offered. No link to the provider's dashboard. No guidance on what to do next. The user has to know to either run --setup again or manually edit ~/.config/tunacode.json.
Findings
Current Architecture
Config File Location
- Path:
~/.config/tunacode.json - Defined in:
constants.py:26(CONFIG_FILE_NAME = "tunacode.json") - Assembled by:
configuration/settings.py:14-17(PathConfig)
API Key Flow (Config -> Agent)
~/.config/tunacode.json
|
v
load_config() -> merge_user_config(defaults, user) [user_config.py:30-70]
|
v
StateManager._session.user_config["env"] [state.py:83]
|
v
_build_api_key_resolver(session) [agent_config.py:239-266]
| closure over env_config dict
v
AgentOptions(get_api_key=resolver) [agent_config.py:381]
|
v
tinyagent calls resolver(provider_id) per request
No os.environ fallback -- API keys come exclusively from the JSON config file. The resolver has one fallback: OPENAI_API_KEY is tried as a universal fallback for non-OpenAI providers (agent_config.py:264).
Relevant Files
| File | Role |
|---|---|
ui/commands/model.py |
/model command, validation gate, error messaging |
ui/screens/model_picker.py |
Provider + model picker modals |
ui/screens/setup.py |
First-run setup wizard with inline API key Input |
configuration/models.py |
validate_provider_api_key(), get_provider_env_var() |
configuration/defaults.py |
Default config shape with empty env vars |
core/agents/agent_components/agent_config.py |
_build_api_key_resolver(), agent creation |
core/ui_api/configuration.py |
Facade re-exporting config functions |
ui/styles/modals.tcss |
CSS for #api-key-input, #error-label (from setup screen) |
ui/renderers/errors.py |
Error panel renderer (no API key-specific entry) |
configuration/user_config.py |
save_config(), load_config_with_defaults() |
Specific Issues Found
Issue 1: validate_provider_api_key ignores os.environ
configuration/models.py:112-125 checks only user_config["env"]. A user with ANTHROPIC_API_KEY exported in their shell will see a false-positive validation failure, even though the key would work at runtime (since _build_api_key_resolver also only checks user_config, meaning it wouldn't work at runtime either -- both are consistent but both ignore the shell env).
Issue 2: Picker flow passes show_config_path=False
model.py:100-106 -- the picker callback (the more common, more interactive path) gives the user LESS information than the direct /model provider:model path which passes show_config_path=True at line 77. The user sees only a transient toast and no config file path.
Issue 3: No inline key entry after validation failure
After the picker is dismissed and validation fails, the user has NO way to enter the key without:
- Running
tunacode --setupagain (restarts everything) - Manually editing
~/.config/tunacode.json(requires knowing the path and format)
There is no "enter your key now" modal in the model selection flow.
Issue 4: No provider dashboard links
The registry at models_registry.json has provider data but no dashboard/signup URLs. A truly helpful UX would tell the user WHERE to get the key.
Issue 5: AuthenticationError has no specific recovery hints
ui/renderers/errors.py has ERROR_SEVERITY_MAP and DEFAULT_RECOVERY_COMMANDS but neither includes AuthenticationError. When the agent fails at runtime due to a missing key, the user gets a generic error panel with no API-key-specific guidance.
Existing Reusable Patterns
The SetupScreen (setup.py:25-145) already has:
Input(password=True, id="api-key-input")for secret key entryStatic("", id="error-label")for inline validation feedbackget_provider_env_var(provider)to resolve the correct env varsave_config()to persist the key
CSS for these widgets exists in modals.tcss (#api-key-input, #error-label).
Key Patterns / Solutions Found
Inline key entry screen pattern:
SetupScreenprovides a complete, tested blueprint for collecting API keys inline. A lightweightApiKeyScreen(Screen[str | None])can be extracted from this pattern.Screen chaining in Textual: The model picker already chains
ProviderPickerScreen -> ModelPickerScreenviapush_screencallbacks. Adding a third screen (ApiKeyScreen) on validation failure follows the same pattern.Config persistence:
save_config(state_manager)already handles writinguser_config["env"][env_var] = keyto disk. The new screen just needs to updatestate_manager.session.user_config["env"]and call save.Fallback resolver:
_build_api_key_resolvercapturesenv_configat agent creation time. After saving a new key,invalidate_agent_cache()forces re-creation, which re-captures the updated env dict.
Knowledge Gaps
Provider dashboard URLs: The
models_registry.jsondoes not contain signup/dashboard URLs for providers. Adding these would require a schema extension and data collection effort. Out of scope for the initial fix but worth tracking.os.environpolicy: Is there an intentional design decision to NOT read API keys fromos.environ? The current codebase is consistent (neither validation nor the resolver checksos.environ), but users coming from other tools may expect env vars to work. Need to decide if this should be supported.Compaction key error:
compaction/controller.py:439raisesMissingCompactionApiKeyErrorbut it's caught and swallowed at line 243-248 (compaction skips silently). This is a separate UX concern but related.
Proposed Solution Direction
Create an ApiKeyEntryScreen that:
- Is pushed when
_validate_provider_api_key_with_notificationfails during model selection - Shows the provider name, the required env var name, and a password input
- On submit: saves key to
user_config["env"], persists config, proceeds with model switch - On cancel: returns to the model picker (not the main chat)
Flow change:
Current: Pick provider -> Pick model -> Validation fails -> Toast + dead end
Proposed: Pick provider -> Pick model -> Validation fails -> ApiKeyEntryScreen -> Save & switch
Quick wins (no new screen):
- Always pass
show_config_path=Truefrom the picker callback - Add
AuthenticationErrortoERROR_SEVERITY_MAPwith recovery hints - Make the
rich_logmessage more actionable (include config path always)
References
ui/commands/model.py-- validation gate and error messagingui/screens/setup.py-- reusable API key input patternui/screens/model_picker.py-- screen chaining patternconfiguration/models.py-- validation logiccore/agents/agent_components/agent_config.py-- API key resolverui/renderers/errors.py-- error rendering (needs API key entry)