fix(config): common config merge overwrites provider-specific values#1682
fix(config): common config merge overwrites provider-specific values#16823351163616 wants to merge 3 commits intofarion1231:mainfrom
Conversation
…take precedence When common config was enabled, apply_common_config_to_settings merged the common config snippet ON TOP of provider settings, silently overwriting user edits to fields present in the snippet (e.g. changing ENABLE_LSP_TOOLS from "1" to "0" in the editor had no effect on disk). Reversed merge order for Claude (JSON), Codex (TOML), and Gemini (env) so that provider-specific values override common config defaults while absent fields are still filled from the snippet. Added regression tests for both override and fill-in behaviors.
farion1231
left a comment
There was a problem hiding this comment.
Thank you for your contribution. Could you please check whether the following issue still exists?
[P1] Array-valued common config still loses shared entries on save
This fixes the scalar/object overwrite bug, but array-valued common config still does not round-trip correctly. The apply path here still relies on json_deep_merge / merge_toml_item, and both helpers treat arrays (and other non-object values) as full replacements. In contrast, remove_common_config_from_settings already strips common-array entries element by element. That means a provider value like ['tool1', 'tool2'] with a common snippet ['tool1'] is persisted as ['tool2'], and this apply step reconstructs only ['tool2'] instead of restoring ['tool1', 'tool2']. As a result, Claude, Codex, and Gemini can still silently drop shared array entries after save.
Mirror the backend merge-order fix on the frontend side: - Reverse deepMerge argument order in updateCommonConfigSnippet and updateTomlCommonConfigSnippet so provider values override common config defaults rather than being overwritten by them - Replace isSubset (value equality) with hasMatchingShape (key existence) in hasCommonConfigSnippet and hasTomlCommonConfigSnippet so the toggle stays checked when the user edits a field that also exists in the snippet - Fix applySnippetToEnv in useGeminiCommonConfig to apply snippet first then let provider env values override, consistent with other app types Add regression tests covering JSON, TOML, and Gemini env precedence.
The remove step strips common config arrays element-by-element, but the apply step was replacing arrays wholesale via json_deep_merge / merge_toml_item. This caused array-valued common config entries (e.g. allowedTools) to lose shared elements after a save round-trip. Add Array match arms to json_deep_merge and merge_toml_item that append only items not already present, using the same subset predicates as the remove path for symmetry. Added round-trip, dedup, and scalar-regression tests for both Claude (JSON) and Codex (TOML) arrays.
|
Thanks for the review! The array round-trip issue you identified has been fixed in the latest commit ( Changes made:
All 11 Rust tests pass ( Additionally, commit |
farion1231
left a comment
There was a problem hiding this comment.
Thank you for your contribution, I still have three review concerns:
P2 Shape-only detection can misclassify legacy/imported Claude and Codex providers as opted in to common config. The new hasMatchingShape check only verifies that matching keys exist, not that the snippet was actually applied before. Because the form still uses initialEnabled ?? inferredHasCommon, this only affects providers that do not already have an explicit commonConfigEnabled flag. If such a provider is opened and saved, the UI state can be persisted as commonConfigEnabled: true, which changes future behavior.
P2 Gemini has the same issue. Its new detection logic now checks only for key presence, so a legacy/imported Gemini provider without explicit commonConfigEnabled can also be treated as opted in after an edit/save cycle.
P2 Frontend array semantics still do not match the backend. The Rust side now uses set-union semantics for arrays during apply and element-wise removal during strip, but the frontend helpers still replace arrays wholesale and only remove them on exact match. That means the editor preview can still diverge from the actual live config for array-valued common config.
Problem
When editing a Claude provider's config (e.g., changing
ENABLE_LSP_TOOLSfrom"1"to"0") and saving, the~/.claude/settings.jsonfile was not updated — the old value persisted silently.Root Cause
In
apply_common_config_to_settings(src-tauri/src/services/provider/live.rs), common config was merged ON TOP of provider settings viajson_deep_merge(provider, common_config). This caused any leaf value present in the common config snippet to always overwrite the corresponding provider-specific value, even when the user explicitly changed it in the editor.The save flow:
normalize_provider_common_config_for_storage— strips exact-match common config fields from provider settings (works correctly, different values are preserved)write_live_with_common_config→apply_common_config_to_settings— merges common config ON TOP, overwriting the user's edit back to the snippet's valueFix
Reversed the merge order to
json_deep_merge(common_config, provider)so that:Applied the same fix for all three config formats:
Testing
cargo test --lib -- live::tests)pnpm typecheckpassespnpm test:unit— 183/185 tests pass; the 2 failures intests/integration/App.test.tsxare pre-existing (confirmed by running on unmodifiedmain)claude_provider_override_wins_over_common_config— verifies provider value takes precedenceclaude_common_config_fills_absent_fields— verifies common config still fills missing fields