Skip to content

fix(config): make env vars win over persisted JSON across all aliases#689

Open
sean-kim05 wants to merge 1 commit into
usestrix:mainfrom
sean-kim05:fix/config-env-precedence
Open

fix(config): make env vars win over persisted JSON across all aliases#689
sean-kim05 wants to merge 1 commit into
usestrix:mainfrom
sean-kim05:fix/config-env-precedence

Conversation

@sean-kim05

Copy link
Copy Markdown

Closes #688

What

_read_json_overrides in strix/config/loader.py is documented to let environment variables outrank the persisted cli-config.json (load_settings: "env vars win, then the JSON file"). Since its result is passed to Settings(**init_kwargs) and pydantic-settings ranks the init source above env, it must not emit a field whose env var is already set.

The old per-field loop decided per-alias and breakd on the first alias found in either env or the file, so it missed two cases:

1. Multi-alias fields. api_key = AliasChoices("LLM_API_KEY", "OPENAI_API_KEY"). With env OPENAI_API_KEY=sk-env and file {"env": {"LLM_API_KEY": "sk-file"}}, the loop hit the file alias first and emitted {"llm": {"api_key": "sk-file"}}load_settings().llm.api_key == "sk-file" instead of the live sk-env. OPENAI_API_KEY/OPENAI_BASE_URL are commonly exported, and persist_current can save the other alias, so a stale key silently wins.

2. Case sensitivity. Settings use case_sensitive=False, but key in os.environ is exact-case, so a lowercase env var (strix_llm) was not seen as set and the file value won.

Change

Decide whether a field is already set in the environment by checking all of its aliases case-insensitively (env_present = {k.upper() for k in os.environ}) before consulting the file:

aliases = [alias.upper() for alias in _aliases_for(finfo)]
if any(alias in env_present for alias in aliases):
    continue  # env wins under some alias; skip the JSON file for this field
for alias in aliases:
    if alias in env_block_upper:
        sub_data[fname] = env_block_upper[alias]
        break

Tests

Added to tests/test_config_loader.py:

  • env_wins_across_field_aliases — env OPENAI_API_KEY, file LLM_API_KEY{} (env wins)
  • env_wins_case_insensitively — lowercase strix_llm set → {}
  • uses_json_when_no_alias_in_environ — no env alias set → file value still used (guard)

The first two fail on the old code and pass with the fix; existing precedence/mapping tests still pass. Full suite: 86 passed; ruff/mypy clean.

Note

Same root cause, left out to keep this focused: persist_current uses os.environ.get(alias.upper()), so a lowercase-set env var is silently not persisted. Happy to address separately if desired.

_read_json_overrides is documented to let env vars outrank the
persisted cli-config.json, but it decided per-alias and broke on the
first alias found in either env or the file. When a multi-alias field
(e.g. api_key via LLM_API_KEY/OPENAI_API_KEY) was set in the env under
one alias but stored in the file under another, the stale file value
was surfaced as an init kwarg and overrode the live env var. A
lowercase env var was also missed (settings use case_sensitive=False).

Decide whether a field is already set in the environment by checking
all of its aliases case-insensitively before consulting the file. Add
regression tests for the cross-alias and case-insensitive cases.

Closes usestrix#688
@greptile-apps

greptile-apps Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR updates config loading so live environment values take precedence over persisted JSON. The main changes are:

  • Case-insensitive detection of environment variables in _read_json_overrides.
  • Cross-alias skipping of persisted JSON when any alias is set in the environment.
  • Regression tests for alias precedence, lowercase environment names, and JSON fallback.

Confidence Score: 5/5

This looks safe to merge.

  • No blocking issues found in the changed code.
  • The updated alias and case handling matches the documented environment-over-JSON behavior.

Important Files Changed

Filename Overview
strix/config/loader.py Updates JSON override loading to check all aliases against the current environment before emitting init kwargs.
tests/test_config_loader.py Adds focused tests for environment-over-JSON precedence across aliases and case-insensitive environment names.

Reviews (1): Last reviewed commit: "fix(config): make env vars win over pers..." | Re-trigger Greptile

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Persisted config can override a live env var, breaking the documented 'env wins' precedence

1 participant