fix: prevent config dual-write conflict in gateway model switch (#37751)#37765
fix: prevent config dual-write conflict in gateway model switch (#37751)#37765alaamohanad169-ship-it wants to merge 1 commit into
Conversation
|
🔧 Test failure fixed — was using Replaced with ✅ Local test run: 3/3 pass in |
|
👋 Ready for review — just flipped from DRAFT. Fixes config dual-write conflict between Desktop & Gateway (#37751). Test bug was fixed (wrong path resolution in |
Replace full-dict save_config() with atomic per-key save_config_value() in the gateway's _handle_model_command --global path. This eliminates the race condition where Gateway's full YAML rewrite could overwrite concurrent Desktop UI config changes (issue NousResearch#37751). The CLI already uses save_config_value (atomic_roundtrip_yaml_update) for model persistence (cli.py:7802-7805). This fix aligns Gateway with the same safe pattern — only the requested dotted keys are updated, preserving any concurrent edits made by another process. Fixes NousResearch#37751
d566a4c to
5eb720f
Compare
Summary
Fixes the Desktop↔Gateway config dual-write conflict described in #37751 by replacing the full-dict
save_config()call with atomic per-keysave_config_value()calls in the gateway's_handle_model_command--globalpath.Root Cause
When
/model <name> --globalis used in the gateway, the old code:save_config(cfg)which does a full YAML rewrite viaatomic_yaml_write()This full-dict rewrite can overwrite concurrent changes made by the Desktop UI (which uses regex field replacements), causing the contradictory config states described in #37751.
Fix
The CLI already avoids this problem by using
save_config_value()(atcli.py:7802-7805) which callsatomic_roundtrip_yaml_update()— this updates only the requested dotted keys while preserving any other changes in the file.This fix aligns the gateway with the same safe pattern:
save_config_value("model.default", result.new_model)save_config_value("model.provider", result.target_provider)(only if provider changed)save_config_value("model.base_url", result.base_url)(only if base URL changed)The scalar model coercion (
model: <name>→model: {default: ...}) is handled internally byatomic_roundtrip_yaml_update(), so the explicit YAML parsing and dict manipulation code is no longer needed.Testing
/model <name> --globalcommand path usessave_config_valueinstead ofsave_configsave_config_valueis already tested intests/cli/test_cli_save_config_value.py/model --globalbehavior is unchanged (still usessave_config_value)Closes #37751