Skip to content

macOS: per-profile HOME isolation in _make_run_env hides ~/Library/Keychains from worker subprocesses, breaks claude CLI auth #29015

@lukelandis3-sketch

Description

@lukelandis3-sketch

Summary

tools/environments/local.py:_make_run_env rewrites HOME to $HERMES_HOME/home/ for every shell subprocess when the per-profile home/ dir exists. This silently breaks any external CLI that resolves its credentials via $HOME/Library/Keychains/login.keychain-db — most notably claude CLI on macOS, which is the exact tool a Hermes kanban worker would shell out to under the recommended "Hermes coordinates, Claude Code implements" delegation pattern.

The symptom is loud and unambiguous from the user's side (claude auth status returns "Not logged in" inside the worker even though the user is authenticated in their interactive shell) but the root cause is buried three layers deep, and there are several plausible-but-wrong leads (audit sessions, start_new_session=True, launchd domain pollution) that consume real debugging time before you find it.

Environment

  • Hermes Agent v0.14.0 (2026.5.16)
  • Claude Code 2.1.145 on macOS Darwin 25.2.0
  • Python 3.11.15
  • Profile setup: orchestrator (gpt-5.5 / openai-codex) dispatches kanban workers as the builder profile, which then invokes claude -p per the claude-cli-kanban-worker skill pattern.

Repro

  1. Create or have a Hermes profile whose $HERMES_HOME/home/ dir exists (hermes profile create <name> populates it; or it auto-creates the first time the bash tool runs).
  2. Authenticate Claude Code via claude /login in your normal Terminal so the macOS Keychain entry is populated.
  3. Verify auth works from the user's shell:
    claude auth status --text
    # → Login method: Claude Max account
  4. Spawn a Hermes worker that invokes claude auth status via the agent's bash tool (any kanban task pointing at a skill that runs claude auth status reproduces; or use hermes -p <profile> chat -q 'run: claude auth status --text').
  5. Worker reports:
    Not logged in. Run claude auth login to authenticate.
    "loggedIn": false, "authMethod": "none", "apiProvider": "firstParty"
    

Root cause

tools/environments/local.py lines 311–317:

from hermes_constants import get_subprocess_home
_profile_home = get_subprocess_home()
if _profile_home:
    run_env["HOME"] = _profile_home

get_subprocess_home() returns $HERMES_HOME/home/ whenever that dir exists. This isolates git/ssh/gh/npm configs per profile — good and intentional. But macOS Claude Code resolves the user's login keychain via $HOME/Library/Keychains/login.keychain-db, and the rewritten HOME has no Library/Keychains/ symlink or directory under it, so the keychain lookup fails and claude falls back to apiProvider: "firstParty" with no credentials.

Bisection that landed on this:

  • ✗ Audit session — Terminal-launched gateway has the right audit session (verified via audit_session_self()), but workers still fail.
  • start_new_session=True / os.setsid — reproduces fine in isolation with the same flags from a Terminal-spawned Python; not the gate.
  • ✗ Launchd gui/<uid> domain env pollution — launchctl kickstart -k produced a clean daemon env, no change.
  • ✓ HOME override — HOME=/tmp/nope claude auth status --text deterministically reproduces "Not logged in"; restoring HOME or symlinking ~/Library/Keychains into $HERMES_HOME/home/Library/ fixes it.

Suggested fix

Two reasonable options, increasing order of effort:

  1. Whitelist Library/Keychains in the HOME isolation. Auto-symlink $REAL_HOME/Library/Keychains into $HERMES_HOME/home/Library/Keychains whenever get_subprocess_home() is active. This preserves all current isolation guarantees and is the minimum needed for any macOS CLI that depends on the login keychain (claude, gh, anything using security framework).

  2. Document the requirement in the profile-init docs and add a hermes doctor check that flags missing home/Library/Keychains symlinks when a profile is set up to use claude CLI (e.g., detects the claude-cli-kanban-worker skill).

Option 1 is what I'd recommend — the breakage isn't user error, it's a Hermes-imposed environmental constraint that quietly defeats the most-recommended delegation pattern.

Workaround for users hitting this today

mkdir -p ~/.hermes/profiles/<profile>/home/Library
ln -sfn ~/Library/Keychains ~/.hermes/profiles/<profile>/home/Library/Keychains

Verify:

HOME=~/.hermes/profiles/<profile>/home claude auth status --text
# → Login method: Claude Max account

Optional (for projects/sessions/trust-list sharing with the user's interactive claude):

ln -sfn ~/.claude.json ~/.hermes/profiles/<profile>/home/.claude.json
ln -sfn ~/.claude      ~/.hermes/profiles/<profile>/home/.claude

Do not delete the profile's home/ dir to "fix" this — it disables HOME isolation entirely and leaks git/npm/ssh state into your real ~/.

Why this matters beyond claude

Anything on macOS that resolves credentials via $HOME/Library/Keychains/ will silently fail from inside a Hermes worker the same way: gh auth status, aws sso login (writes there), op (1Password CLI), security find-generic-password, etc. Claude is the most user-visible case because the recommended "Claude Code as implementer" delegation pattern routes through it, but it's a general issue.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Medium — degraded but workaround existsbackend/localLocal shell executioncomp/agentCore agent loop, run_agent.py, prompt buildertype/bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions