Compare commits

...

1 Commits

Author SHA1 Message Date
bearsyankees
d0768a5c54 fix(config): preserve file overrides on startup 2026-08-25 17:34:30 -04:00
2 changed files with 63 additions and 17 deletions

View File

@@ -54,21 +54,29 @@ def apply_config_override(path: Path) -> None:
def persist_current() -> None:
"""Write currently-set env vars to the active config file (0o600)."""
"""Merge currently-set env vars into the active config file (0o600)."""
s = load_settings()
target = _override or _DEFAULT_PATH
target.parent.mkdir(parents=True, exist_ok=True)
env_block: dict[str, str] = {}
for sub_name in s.model_fields:
env_block = {
key: value
for key, value in _read_env_block(target).items()
if isinstance(value, str)
}
process_env = {key.upper(): value for key, value in os.environ.items()}
for sub_name in type(s).model_fields:
sub_model = getattr(s, sub_name)
if not isinstance(sub_model, BaseModel):
continue
for finfo in type(sub_model).model_fields.values():
for alias in _aliases_for(finfo):
value = os.environ.get(alias.upper())
aliases = [alias.upper() for alias in _aliases_for(finfo)]
for alias in aliases:
value = process_env.get(alias)
if value:
env_block[alias.upper()] = value
for sibling_alias in aliases:
env_block.pop(sibling_alias, None)
env_block[alias] = value
break
write_secret_text(target, json.dumps({"env": env_block}, indent=2))
@@ -93,17 +101,7 @@ def _read_json_overrides(path: Path) -> dict[str, dict[str, Any]]:
Only includes keys whose env var is NOT already set, so env always
wins over the persisted file.
"""
if not path.exists():
return {}
try:
data = json.loads(path.read_text(encoding="utf-8"))
except (json.JSONDecodeError, OSError):
return {}
env_block = data.get("env", {}) if isinstance(data, dict) else {}
if not isinstance(env_block, dict):
return {}
env_block_upper = {str(k).upper(): v for k, v in env_block.items()}
env_block_upper = _read_env_block(path)
env_present = {k.upper() for k in os.environ}
nested: dict[str, dict[str, Any]] = {}
@@ -123,3 +121,17 @@ def _read_json_overrides(path: Path) -> dict[str, dict[str, Any]]:
if sub_data:
nested[sub_name] = sub_data
return nested
def _read_env_block(path: Path) -> dict[str, Any]:
"""Return a config file's environment block with normalized keys."""
if not path.exists():
return {}
try:
data = json.loads(path.read_text(encoding="utf-8"))
except (json.JSONDecodeError, OSError):
return {}
env_block = data.get("env", {}) if isinstance(data, dict) else {}
if not isinstance(env_block, dict):
return {}
return {str(key).upper(): value for key, value in env_block.items()}

View File

@@ -208,6 +208,40 @@ def test_persist_current_writes_env_block(tmp_path: Path, monkeypatch: pytest.Mo
}
def test_persist_current_preserves_file_only_values(tmp_path: Path) -> None:
target = tmp_path / "cli-config.json"
expected = {
"env": {
"STRIX_LLM": "openrouter/z-ai/glm-5.3",
"LLM_API_KEY": "sk-from-file",
}
}
target.write_text(json.dumps(expected), encoding="utf-8")
loader.apply_config_override(target)
loader.persist_current()
assert json.loads(target.read_text(encoding="utf-8")) == expected
def test_persist_current_replaces_stale_alias_from_environment(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
target = tmp_path / "cli-config.json"
target.write_text(
json.dumps({"env": {"LLM_API_KEY": "sk-from-file"}}),
encoding="utf-8",
)
monkeypatch.setenv("OPENAI_API_KEY", "sk-from-env")
loader.apply_config_override(target)
loader.persist_current()
assert json.loads(target.read_text(encoding="utf-8")) == {
"env": {"OPENAI_API_KEY": "sk-from-env"}
}
def test_persist_current_sets_0600_mode(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("STRIX_LLM", "persisted-model")
target = tmp_path / "cli-config.json"