Summary
hermes auth remove openai-codex <target> can leave an extra device_code credential behind when ~/.codex/auth.json exists. The removal path calls load_pool(), and load_pool() auto-imports Codex CLI tokens into the Hermes auth store before the target credential is removed.
This means a command that should only delete one pool entry can also mutate ~/.hermes/auth.json by seeding a new singleton entry from the external Codex CLI state.
Affected files / lines
hermes_cli/auth_commands.py:303-315 — auth_remove_command() resolves the target via load_pool(provider)
agent/credential_pool.py:1341-1363 — load_pool() always runs singleton seeding before removal
agent/credential_pool.py:1155-1189 — _seed_from_singletons() imports ~/.codex/auth.json into providers.openai-codex / credential_pool.openai-codex
Why this is a bug
auth remove is expected to remove exactly the requested credential. Instead, on machines where Codex CLI is logged in, the command can persist a fresh device_code entry that the user did not ask to add.
Two concrete bad outcomes:
- Removing a label-targeted manual entry still leaves two credentials in the pool (the surviving manual entry + an imported singleton).
- Removing target
2 can act on the wrong logical set of credentials because load_pool() has already injected an extra seeded entry into the pool state.
Minimal reproduction
- Create
~/.hermes/auth.json with two manual openai-codex pool entries (work-account, personal-account).
- Create
~/.codex/auth.json with a valid Codex access/refresh token pair.
- Run
hermes auth remove openai-codex personal-account.
- Inspect
~/.hermes/auth.json.
Actual behavior
The command prints that it removed personal-account, but the resulting pool still contains:
work-account
- a newly imported
device_code entry seeded from ~/.codex/auth.json
I reproduced this locally with a fully isolated temp HERMES_HOME + temp CODEX_HOME; after auth_remove_command(), the pool persisted:
work-account (manual:device_code)
codex@example.com (device_code)
The selected test chunk also reproduces this on default-branch main:
pytest -q tests/hermes_cli/test_auth_commands.py ...
- failing tests:
test_auth_remove_accepts_label_target
test_auth_remove_prefers_exact_numeric_label_over_index
Expected behavior
auth remove should only remove the requested credential and should not auto-import external Codex CLI credentials as a side effect.
Suggested investigation direction
Possible fix directions:
- Make
auth_remove_command() operate on the persisted pool entries without triggering singleton seeding/import.
- Or teach
load_pool() / _seed_from_singletons() to skip Codex CLI auto-import for destructive management commands like auth remove.
- At minimum, prevent
openai-codex singleton seeding from mutating the pool during target resolution.
Summary
hermes auth remove openai-codex <target>can leave an extradevice_codecredential behind when~/.codex/auth.jsonexists. The removal path callsload_pool(), andload_pool()auto-imports Codex CLI tokens into the Hermes auth store before the target credential is removed.This means a command that should only delete one pool entry can also mutate
~/.hermes/auth.jsonby seeding a new singleton entry from the external Codex CLI state.Affected files / lines
hermes_cli/auth_commands.py:303-315—auth_remove_command()resolves the target viaload_pool(provider)agent/credential_pool.py:1341-1363—load_pool()always runs singleton seeding before removalagent/credential_pool.py:1155-1189—_seed_from_singletons()imports~/.codex/auth.jsonintoproviders.openai-codex/credential_pool.openai-codexWhy this is a bug
auth removeis expected to remove exactly the requested credential. Instead, on machines where Codex CLI is logged in, the command can persist a freshdevice_codeentry that the user did not ask to add.Two concrete bad outcomes:
2can act on the wrong logical set of credentials becauseload_pool()has already injected an extra seeded entry into the pool state.Minimal reproduction
~/.hermes/auth.jsonwith two manualopenai-codexpool entries (work-account,personal-account).~/.codex/auth.jsonwith a valid Codex access/refresh token pair.hermes auth remove openai-codex personal-account.~/.hermes/auth.json.Actual behavior
The command prints that it removed
personal-account, but the resulting pool still contains:work-accountdevice_codeentry seeded from~/.codex/auth.jsonI reproduced this locally with a fully isolated temp
HERMES_HOME+ tempCODEX_HOME; afterauth_remove_command(), the pool persisted:work-account(manual:device_code)codex@example.com(device_code)The selected test chunk also reproduces this on default-branch
main:pytest -q tests/hermes_cli/test_auth_commands.py ...test_auth_remove_accepts_label_targettest_auth_remove_prefers_exact_numeric_label_over_indexExpected behavior
auth removeshould only remove the requested credential and should not auto-import external Codex CLI credentials as a side effect.Suggested investigation direction
Possible fix directions:
auth_remove_command()operate on the persisted pool entries without triggering singleton seeding/import.load_pool()/_seed_from_singletons()to skip Codex CLI auto-import for destructive management commands likeauth remove.openai-codexsingleton seeding from mutating the pool during target resolution.