fix(setup): escape API key in config.toml so it survives relaunch (#288)#293
Merged
Conversation
The first-run setup wizard wrote config.toml by hand-formatting the TOML
(`format!("api_key = \"{}\"", key)`). That does not escape the value, so:
- a key containing `\` was silently mangled on reload (`\b` decoded to
a backspace), and
- a key containing `"` made config.toml unparseable.
Either way the key was effectively "forgotten" on the next launch even
though setup looked successful — and the in-process env var set during
the wizard masked it for the current session. This is common on
OpenAI-compatible "Other" endpoints whose tokens contain such
characters.
Render the config through the `toml` serializer instead (correct
escaping for every value), write it atomically with owner-only `0600`
permissions since it holds a secret, and surface save failures to the
user instead of swallowing them with `let _ =`.
Adds unit tests covering the backslash and quote cases plus the
empty/ollama-sentinel omission paths.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`prune_skips_non_json_files` pinned the session's `updated_at` to a hardcoded `2026-04-23` but compared it against `chrono::Utc::now()`. Once the wall clock passed 2026-05-23 the kept session aged beyond the 30-day threshold and was pruned, so `stats.removed` became 1 and the assertion failed (currently breaking Test + Coverage on CI; the Windows matrix leg is fail-fast-cancelled as a result). Pin `now` to a fixed instant, matching the sibling `prune_*` tests, so the test no longer depends on the current date. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Member
Author
|
Pushed a follow-up commit to fix the red CI. The failures weren't from the setup change — they all traced to a pre-existing time-bomb test, Fixed by pinning |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #288.
Problem
The reporter set up an API key (via the "Other"/custom OpenAI-compatible provider), it worked for that session, but every relaunch "forgot" the key from
config.toml— only settingAGENT_CODE_API_KEYin the environment worked.Root cause
The load path is fine — I verified end-to-end that
Config::load()reads a setup-writtenapi_keyback through the migration + merge pipeline. The bug is on the write side.crates/cli/src/ui/setup.rs::write_confighand-formatted the TOML:That does not escape the value, so a key containing a backslash or a double quote is silently broken on the next load:
sk-normaltoken-123sk-normaltoken-123✓sk-with\backslashsk-with␈ackslash✗ (\b→ backspace)sk-with"quoteIn all the broken cases setup still looked successful (and the in-process env var set during the wizard kept the current session working), so the failure only surfaced on the next launch — exactly the reported symptom. Such keys are common on the "Other"/custom endpoints the reporter selected.
Fix
write_confignow:tomlcrate (render_config_toml), so every value — the API key in particular — is escaped correctly.0600permissions viaatomic_write_secret(the file holds a secret; this matches how the rest of the codebase — onboarding,config_tool, migrations — already treatsconfig.toml). Existing files keep their mode.let _ =, so a failed save no longer masquerades as a successful setup.Tests
Added unit tests in
ui::setup::testscovering:\survives (the silent-corruption case)"survives (the unparseable case)base_url,model,permissions,ui) preservedollamasentinel are omittedcargo fmt --checkandcargo clippyare clean.🤖 Generated with Claude Code