fix(desktop): stop PUT /api/config storing its own transport envelope - #823
Merged
Conversation
A real config had a full stale snapshot nested under a `config` key, with
the live `stt` (voice transcription) block stranded inside it — so the
backend, which reads the top level, never saw the user's voice settings.
The renderer sends `{"config": {...}}` and autosaves the WHOLE record it
last read, so one mis-nested write is copied forward on every later save,
and `_deep_merge` only ever adds — nothing could ever remove it. `config` is
not a key in the config schema (the file holds default_provider / providers /
session / settings / env / projects / … at the top level), so its presence
can only ever be an envelope stored by mistake.
- Unwrap repeatedly on the way in. A DOUBLE wrap is the shape that does the
damage: unwrapping once leaves a `config` key that then merges in as data.
- Drop the envelope when saving, so a config that already picked one up is
repaired by its next ordinary write rather than needing a migration.
- Strip it on the way out too, so a stored envelope can't be round-tripped
straight back by the renderer's whole-record autosave.
Credentials still survive the redacted round trip — that is what the
deep-merge is for, and there is a test for it.
Co-Authored-By: Claude <noreply@anthropic.com>
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.
A real
~/.clawcodex/config.jsonhad a full stale snapshot nested under aconfigkey, with the livestt(voice transcription) block stranded inside it — so the backend, which reads the top level, never saw the configured voice settings.The nested block was identifiable as a redacted
GET /api/configresponse (providers carrying onlybase_url/default_model, noenv), and stale — itsopenaiprovider still pointed at the endpoint the user had since changed.Why it stuck
saveClawCodexConfigsends{"config": {...}}, and the settings panel autosaves the whole record it last read. So one mis-nested write is copied forward on every later save, and_deep_mergeonly ever adds — nothing could ever remove it. Worse, the envelope shadows real settings: anything written inside it is invisible to a backend that reads the top level.configis not a key in the config schema, so its presence can only ever be an envelope stored by mistake.Fix
configkey behind, which then merges in as data.Credentials still survive the redacted round trip — that is what the deep-merge is for, and there is a test asserting an
api_keyis not lost.Testing
New
tests/server/test_desktop_config_envelope.py: single and double unwrap, bare records untouched, a wrapped save landing at the top level with credentials intact, and an existing envelope repaired by the next save. Full Python suite: 9912 passed, 15 skipped.🤖 Generated with Claude Code