Skip to content

fix(codex-runtime): de-dup [plugins.X] tables and stop leaking HERMES_HOME into config.toml (#26250)#26260

Merged
kshitijk4poor merged 2 commits into
NousResearch:mainfrom
kshitijk4poor:fix/codex-runtime-26250
May 15, 2026
Merged

fix(codex-runtime): de-dup [plugins.X] tables and stop leaking HERMES_HOME into config.toml (#26250)#26260
kshitijk4poor merged 2 commits into
NousResearch:mainfrom
kshitijk4poor:fix/codex-runtime-26250

Conversation

@kshitijk4poor

Copy link
Copy Markdown
Collaborator

Summary

Fixes the three independent bugs in hermes codex-runtime migrate reported in #26250. Each one corrupts ~/.codex/config.toml in a way that crashes codex on next startup with a TOML parse error.

This PR builds on @steezkelly's #25857 (Bug A — top-level default_permissions via _insert_managed_block_at_top_level) by cherry-picking it onto current main and then adding fixes for Bugs B and C with regression tests.

Bugs fixed

Bug Where Fix
Adefault_permissions lands inside a previous [mcp_servers.X] table on re-render migrate() appended the managed block after pre-existing user tables, so the root key got scoped to the wrong table. Insert the managed block before the first table header (commit by @steezkelly, cherry-picked from #25857).
B[plugins."<name>@<marketplace>"] tables duplicated when codex itself installed plugins before hermes' migrate ran Codex's own [plugins.*] tables outside the managed block survive _strip_existing_managed_block(), then _query_codex_plugins() reports the same plugins and we re-emit them inside the managed block. Result: duplicate table headers → codex's strict TOML parser rejects the file. New _strip_unmanaged_plugin_tables() drops [plugins.*] tables from the user-content portion of the file, but only when plugin/list succeeded — otherwise we'd silently lose plugins we can't re-emit. plugin/list is the source of truth when it answers.
CHERMES_HOME from os.environ leaks pytest-tempdir paths into the user's real ~/.codex/config.toml _build_hermes_tools_mcp_entry() read os.environ["HERMES_HOME"] at migrate time. A sibling pytest's monkeypatch.setenv("HERMES_HOME", tmp_path) therefore burned a transient pytest tempdir path into the user's actual codex config; once pytest reaped the tempdir, every codex-routed hermes-tools call failed silently. Derive HERMES_HOME from get_hermes_home() (canonical profile-aware resolver) and refuse to emit obvious test-tempdir paths via _looks_like_test_tempdir() as belt-and-suspenders. Also patch test_enable_succeeds_when_codex_present to mock migrate() so the suite stops touching the user's real ~/.codex/.

E2E verification (reproduces the issue's exact pre-state)

# Pre-state: user [mcp_servers.omx_team_run] + codex-installed [plugins."tasks@openai-curated"]
# HERMES_HOME=/private/var/folders/.../pytest-of-.../...

On origin/main:

tomllib.TOMLDecodeError: Cannot declare ('plugins', 'tasks@openai-curated') twice (at line 13, column 32)
HERMES_HOME leak: /private/var/folders/xx/pytest-of-user/pytest-137/popen-gw2/test_X/hermes_test

On this branch:

✓ default_permissions is top-level
✓ No duplicate [plugins.X] table headers
✓ No pytest tempdir leaked into MCP env
✓ tomllib.loads(new_text) parses cleanly

Test plan

bash scripts/run_tests.sh tests/hermes_cli/test_codex_runtime_plugin_migration.py tests/hermes_cli/test_codex_runtime_switch.py
# 95 passed in 1.76s

7 new regression tests:

  • TestStripUnmanagedPluginTables (4 tests): unit + end-to-end migrate dedup + preservation when plugin/list fails.
  • TestHermesHomeLeakGuard (4 tests): tempdir detector positive/negative + e2e pytest-tempdir refusal + real-path passthrough.
  • test_enable_succeeds_when_codex_present now patches migrate() (closes the test-leak path).

Attribution

Closes #26250

steezkelly and others added 2 commits May 15, 2026 14:37
…_HOME into config.toml

Builds on @steezkelly's Bug A fix (NousResearch#25857, top-level default_permissions
via _insert_managed_block_at_top_level) by addressing the other two
config-corruption bugs described in NousResearch#26250:

Bug B (duplicate [plugins.X] tables)
  - Codex itself writes [plugins."<name>@<marketplace>"] tables to
    config.toml when the user runs `codex plugins enable` directly,
    before hermes-agent's managed block exists. On the next migrate run,
    _query_codex_plugins() re-discovers the same plugins via plugin/list
    and render_codex_toml_section() re-emits them inside the managed
    block. Codex's strict TOML parser then rejects the duplicate table
    header on startup.
  - Add _strip_unmanaged_plugin_tables() that drops [plugins.*] tables
    from the user-content portion of the file. Only run it when
    plugin/list succeeded — if the RPC failed we can't re-emit and
    must preserve the user's tables. plugin/list is the source of
    truth when it answers.

Bug C (HERMES_HOME pytest-tempdir leak into ~/.codex/config.toml)
  - _build_hermes_tools_mcp_entry() read HERMES_HOME directly from
    os.environ, so a sibling pytest's monkeypatch.setenv("HERMES_HOME",
    tmp_path) silently burned a transient pytest tempdir into the
    user's real ~/.codex/config.toml. After pytest reaped the tempdir,
    every codex-routed hermes-tools tool call failed silently.
  - Derive HERMES_HOME from get_hermes_home() (the canonical resolver
    that goes through the profile-aware path) and refuse to emit
    obvious test-tempdir paths via _looks_like_test_tempdir() as
    belt-and-suspenders for any other callsite that forgets to patch
    migrate().
  - test_enable_succeeds_when_codex_present in test_codex_runtime_switch.py
    invoked the real migrate() (no mock), writing to Path.home() / .codex
    using whatever HERMES_HOME the running pytest session had set. Add
    the same migrate patch the other apply() tests already use, so the
    suite stops touching the user's real ~/.codex/config.toml.

E2E verification (replicating the issue's repro):
  - Pre-state config.toml with user [mcp_servers.omx_team_run] +
    codex-installed [plugins."tasks@openai-curated"],
    HERMES_HOME="/private/var/folders/.../pytest-of-.../..."
  - On origin/main: tomllib refuses to load the result with
    "Cannot declare ('plugins', 'tasks@openai-curated') twice" AND
    the pytest-tempdir HERMES_HOME is burned in.
  - On this branch: file parses cleanly, default_permissions is
    top-level, exactly one [plugins."tasks@openai-curated"] table
    inside the managed block, no HERMES_HOME in the MCP env.

7 new regression tests covering all three bugs + the test-leak guard.
`bash scripts/run_tests.sh tests/hermes_cli/test_codex_runtime_*.py` —
95 passed, 0 failed.

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

Labels

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.

hermes codex-runtime migrate produces invalid ~/.codex/config.toml (duplicate keys, scoped default_permissions, leaked HERMES_HOME)

3 participants