Skip to content

fix(config): dedupe duplicate KEY= lines in save_env_value#20100

Open
0xDevNinja wants to merge 1 commit intoNousResearch:mainfrom
0xDevNinja:fix/8270-env-dedupe-duplicate-keys
Open

fix(config): dedupe duplicate KEY= lines in save_env_value#20100
0xDevNinja wants to merge 1 commit intoNousResearch:mainfrom
0xDevNinja:fix/8270-env-dedupe-duplicate-keys

Conversation

@0xDevNinja
Copy link
Copy Markdown
Contributor

What does this PR do?

Hardens hermes_cli.config.save_env_value to remove stale duplicate KEY= lines after replacing the first match. python-dotenv resolves duplicate keys with last-occurrence-wins, so updating only the first match (current behaviour) and leaving older duplicates further down the file means the wrong value still loads at startup.

Reproducer:

>>> from dotenv import dotenv_values
>>> # .env contains:
>>> #   OPENROUTER_API_KEY=fresh-good
>>> #   OPENROUTER_API_KEY=stale-broken
>>> dotenv_values('.env')
{'OPENROUTER_API_KEY': 'stale-broken'}

This surfaced in #8270 — multiple users (urpid, healthymecd, dreamsmaner) reported HTTP 400 on all OpenRouter models despite a freshly-saved key, traced to a stale entry left at the bottom of .env by an earlier setup attempt or by hermes model re-adding the same key. The visible top-of-file entry looked correct; the invisible bottom-of-file entry was the one that loaded.

Fix: walk the file once, replace the first hit with the new value, drop the rest. Append-when-not-found path is unchanged. No behaviour change for the (overwhelming) clean-file case.

Scope note: #8270 has several distinct root causes in its comment thread (mis-pasted keys, OpenRouter tool-format 400s on certain models, etc.). This PR only addresses the duplicate-key class — the others are separate.

Related Issue

Refs #8270

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 🔒 Security fix
  • 📝 Documentation update
  • ✅ Tests (adding or improving test coverage)
  • ♻️ Refactor (no behavior change)
  • 🎯 New skill (bundled or hub)

Changes Made

  • hermes_cli/config.py: rewrite the find-or-append loop in save_env_value (lines 4232-4259) to replace the first match in place, drop subsequent occurrences of the same key, and only append when no match exists at all. Comment captures the dotenv last-wins rationale.
  • tests/hermes_cli/test_config.py: new TestSaveEnvValueDeduplication with three cases — collapse-existing-duplicates (writes load back as the new value), preserve-first-position (other keys keep their order), and no-op-when-clean.

How to Test

# 1. Reproduce the symptom on a clean checkout
printf 'OPENROUTER_API_KEY=stale-broken\nOTHER=keep\nOPENROUTER_API_KEY=also-stale\n' > /tmp/.env
HERMES_HOME=/tmp python -c "from hermes_cli.config import save_env_value, load_env; save_env_value('OPENROUTER_API_KEY', 'fresh-good'); print(open('/tmp/.env').read()); print('loaded:', load_env()['OPENROUTER_API_KEY'])"
# Before the fix: file still contains the stale-broken trailing duplicate, load_env returns 'also-stale'.
# After the fix:  only OPENROUTER_API_KEY=fresh-good remains, load_env returns 'fresh-good'.

# 2. Unit tests
pytest tests/hermes_cli/test_config.py::TestSaveEnvValueDeduplication -v   # 3 pass
pytest tests/hermes_cli/test_config.py::TestSaveEnvValueSecure tests/hermes_cli/test_config.py::TestSanitizeEnvLines -q   # all adjacent tests still green

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run pytest tests/ -q and all tests pass (pre-existing flakes in gateway_service / web_server / bedrock unrelated to this change)
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform: macOS 14.2 (Darwin 23.2.0)

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) — N/A (rationale captured inline)
  • I've updated cli-config.yaml.example if I added/changed config keys — N/A
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — N/A
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guide — string-only line-list manipulation, no platform-specific behaviour.
  • I've updated tool descriptions/schemas if I changed tool behavior — N/A

python-dotenv resolves duplicate keys with last-occurrence-wins, but
save_env_value previously updated only the first occurrence and left
later duplicates intact. So writing a fresh OPENROUTER_API_KEY at the
top of ~/.hermes/.env did not take effect when a stale entry lingered
at the bottom — startup re-loaded the stale value and user-visible
behaviour was an HTTP 400 against a key the user had just "saved".

Replace the first match in-place and drop subsequent occurrences so the
written value is the one that loads. Three regression tests cover the
collapse-existing-duplicates, position-preserving, and no-op-when-clean
paths.

Refs NousResearch#8270
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/cli CLI entry point, hermes_cli/, setup wizard area/config Config system, migrations, profiles labels May 5, 2026
@alt-glitch
Copy link
Copy Markdown
Collaborator

Related to #6183 — both address stale/duplicate entries in .env via save_env_value. #6183 targets placeholder secrets; this PR targets duplicate KEY= lines. Same code path, different class of bug.

1 similar comment
@alt-glitch
Copy link
Copy Markdown
Collaborator

Related to #6183 — both address stale/duplicate entries in .env via save_env_value. #6183 targets placeholder secrets; this PR targets duplicate KEY= lines. Same code path, different class of bug.

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

Labels

area/config Config system, migrations, profiles comp/cli CLI entry point, hermes_cli/, setup wizard P2 Medium — degraded but workaround exists type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants