fix(auth): _auth_lock_path follows symlinks so shared auth.json serializes across processes - #62945
fix(auth): _auth_lock_path follows symlinks so shared auth.json serializes across processes#62945vkrmch wants to merge 1 commit into
Conversation
…lizes `_auth_lock_path()` returned the caller's LOCAL `~/.hermes/auth.lock`. That's fine solo, but silently breaks the shared-auth patterns requested in NousResearch#19436 / NousResearch#29530 / NousResearch#38176 — profiled workers, delegated agents, or multi-agent hosts symlinking `~/.hermes/auth.json` to one canonical file. In that setup each process holds a DIFFERENT flock while writing to the SAME real file, so cross-process load-modify-save races can persist a partial store that purges providers a peer just added. Repro on a 3-agent shared-Codex host: `credential_pool` went 5 providers → 2 in one refresh cycle; codex + xai + copilot all wiped, `active_provider` cleared. Full re-auth required. Fix resolves the symlink target so all sharers lock the same file. Non- symlink setups are unchanged (realpath returns the original path). Related: NousResearch#8040 (same class — process-local threading.Lock in credential_pool.py — this PR fixes only the auth.py surface).
|
Thanks for the focused regression fix. The premise remains present on current main: The change resolves the relevant target without altering normal-path behavior. It also fits the existing write behavior:
Automated hermes-sweeper review. |
Summary
_auth_lock_path()returned the caller's LOCAL~/.hermes/auth.lock— silently broke shared-auth via symlink (the pattern the community is already DIY-ing per feat(profiles): native auth-sharing pattern across forked profiles #19436 / Profiled workers need a shared auth home separate from isolated HERMES_HOME #29530 / Share Codex CLI auth across Hermes gateway profiles #38176).realpathreturns the original path).Motivation
I hit this on a 3-agent host sharing one Codex OAuth via
~/.hermes/auth.json→/var/lib/hermes-oauth/auth.jsonsymlinks. After the first refresh cycle, the sharedauth.jsonwent from 5 providers (gemini,openai-codex,openrouter,xai,copilot) to 2 (gemini,openrouter).openai-codex,xai,copilotall wiped fromcredential_pool.active_providercleared.providersmap emptied. Required a full OAuth re-auth to recover.Traced to
_auth_lock_path()— thefcntl.flockwas on~/agent-x/.hermes/auth.lock, not on the target of the symlink, so cross-process serialization silently didn't work.Related
agent/credential_pool.py's process-localthreading.Lock. That issue suggests using thefcntl.flockpattern fromhermes_cli/auth.pyas a fix template — which is exactly the code path this PR corrects. So if Bug: credential pool TOCTOU — process-local lock doesn't protect cross-process file access #8040 gets that fix without this PR, the shared-auth case is still broken. Left a comment there flagging it: Bug: credential pool TOCTOU — process-local lock doesn't protect cross-process file access #8040 (comment)Fix
hermes_cli/auth.py::_auth_lock_pathnow resolves the auth.json symlink and places the lock next to the REAL file:<target-dir>/auth.lock— cross-process load-modify-save is properly serialized.realpathreturns the original path; lock stays at~/.hermes/auth.lock— identical to today.realpathreturns the (non-existent) target path; the flock is created next to it, and a future creator of the shared file picks up the same lock.Tests
Added
tests/hermes_cli/test_auth_lock_path_symlink.pywith 4 cases:auth.jsonvia symlink resolve to the SAME lock (the regression this fixes).auth.json: lock sits next to it (behavior unchanged).auth.jsonyet: lock path derives from_auth_file_path()unchanged.All 4 pass locally.
Test plan
_auth_lock_path()returns the shared target/var/lib/hermes-oauth/auth.lockfor all agents; the shared lock file gets created group-writable on first acquisition; no observed provider-purge in subsequent refresh cycles.🤖 Generated with Claude Code
https://claude.ai/code/session_01CmDNdF6D8TVXRohg17tqNU