feat(utils): add safe_expanduser() — crash-free path expansion for HOME-unset environments - #41870
feat(utils): add safe_expanduser() — crash-free path expansion for HOME-unset environments#41870rickychen-xm wants to merge 1 commit into
Conversation
Path.expanduser() raises RuntimeError('Could not determine home
directory') when HOME is unset and pwd.getpwuid() fails. This
condition surfaces in launchd-managed daemons (common macOS gateway
deployment), containerized runs, and sudo -E invocations.
safe_expanduser() wraps the call in a (RuntimeError, OSError) catch
and returns the original path (or an explicit default) instead of
crashing — matching what virtually every caller in this codebase
wants when HOME can't be resolved.
Changes:
- utils.py: new safe_expanduser() function with full docstring
- agent/subdirectory_hints.py: switch from Path().expanduser()
to safe_expanduser() as the first production adoption
- tests/test_utils_expanduser.py: 7 tests covering normal,
edge-case, and failure-path behavior
|
Hi @rickychen-xm — flagging an overlap so it doesn't surprise either of us at merge time. My PR #29433 ( Same root cause, different shape — your helper is the better long-term answer (reusable across the other vulnerable sites the reviewers on #29433 flagged: If #29433 lands first (approved + rebased, awaiting maintainer), |
|
Thanks for the focused helper and for documenting the deployment failure mode. The crash-free behavior in this PR’s sole production adoption is already implemented on current Automated hermes-sweeper review evidence:
Closing as implemented on main. |
What does this PR do?
Adds a shared
safe_expanduser()helper toutils.pyand adopts it inagent/subdirectory_hints.pyas the first production caller. The helper wrapsPath.expanduser()so it never raisesRuntimeError("Could not determine home directory")— instead it returns the original path (or an explicitdefault) when HOME cannot be resolved.Related Issue
No existing issue. Surfaced in real
gateway.logtraces on a fresh macOS install of Hermes —agent/subdirectory_hints.py:130crashes whenever a tool call passes a~/-prefixed path throughSubdirectoryHintTracker._add_path_candidatesfrom a context where Python cannot determine HOME. Concrete trigger seen locally:This happens when the macOS launchd-spawned gateway daemon's plist lacks
HOMEinEnvironmentVariables, ANDpwd.getpwuid()lookup fails (rare uid mapping issue). The same class of crash hits Docker/k8s containers with stripped passwd entries andsudo -Einvocations.Type of Change
Changes Made
utils.py(+50 lines): newsafe_expanduser(path, default=None) -> Pathunder a new "Path Helpers" section. CatchesRuntimeErrorandOSError; returns the original path by default, or a caller-provideddefaultwhen supplied.agent/subdirectory_hints.py(+3, -1): importsafe_expanduserfromutils; replace the barePath(raw_path).expanduser()at line 130 withsafe_expanduser(raw_path). The surroundingtry/except (OSError, ValueError)is left in place —safe_expanduseronly neutralizes the HOME-resolution failure mode, not the subsequentresolve()and ancestor-walk errors.tests/test_utils_expanduser.py(+72 lines): 7 tests covering:~/expansion when HOME is set~paths pass through unchangedPathinstances accepted alongside stringsdefaultparameter honored (and coerced toPath)pwd.getpwuidmonkeypatched to raiseKeyError→ original path returned (not a crash)default→ default returnedHow to Test
Expected output:
Checklist
Code
feat(utils): ...)pytest tests/test_utils_expanduser.py tests/test_utils_truthy_values.py tests/agent/— 11 utils tests pass, all agent tests pass except one pre-existing failure on main unrelated to this change (test_anthropic_adapter.py::test_prefers_oauth_token_over_api_keyis environment-sensitive to host~/.claude/state)Documentation & Housekeeping
Path.expanduser()— N/A for separate docs filecli-config.yaml.exampleCONTRIBUTING.md/AGENTS.mdPath.expanduser()on Windows can also raise whenUSERPROFILE/HOMEDRIVE+HOMEPATHare all missing — same catch handles it.Follow-up
This PR is intentionally narrow — it ships the helper + one adopter so the API can be reviewed in isolation. A follow-up PR will sweep the ~49 other bare
Path().expanduser()/os.path.expanduser()call sites acrosshermes_cli/,agent/,cron/,gateway/,tools/,acp_adapter/, andplugins/. Splitting it avoids a giant unreviewable diff and lets the API land first.