fix(constants): display_hermes_home returns absolute path when HERMES_HOME == HOME#21929
Open
li0near wants to merge 1 commit into
Open
fix(constants): display_hermes_home returns absolute path when HERMES_HOME == HOME#21929li0near wants to merge 1 commit into
li0near wants to merge 1 commit into
Conversation
1e71a92 to
88803d4
Compare
…_HOME == HOME
The ~/ shorthand is only meaningful if the reader can resolve it.
`~` universally means "$HOME for the current user". Whoever sees a
description like `Relative paths resolve under ~/./scripts/.` —
human or LLM — will expand `~` against their own $HOME, not against
HERMES_HOME, and look for files in the wrong place.
That mismatch fires in the official Docker image, where:
Dockerfile:21 RUN useradd -u 10000 -m -d /opt/data hermes
Dockerfile:86 ENV HERMES_HOME=/opt/data
set the unix user's home directory equal to HERMES_HOME. Inside the
agent process, `Path.home()` and `get_hermes_home()` then resolve
to the same path. The old implementation:
home.relative_to(Path.home()) # = Path('.')
return "~/" + str(...) # = "~/."
produced `~/.`, which concatenated into the cronjob tool description
as `~/./scripts/`. Agents reading that string place script files
under $HOME (`/opt/data/home`) instead of HERMES_HOME (`/opt/data`),
and the cron scheduler — which resolves script paths against
`HERMES_HOME/scripts/` — fails to find them.
Fix: when `home == Path.home()` the shorthand has nothing to shorten,
so return the absolute path. Standard installs (HERMES_HOME under
HOME) and custom paths outside HOME are unchanged.
Tests:
- test_default_install_uses_shorthand — happy path, ~/.hermes form.
- test_hermes_home_equals_user_home_returns_absolute — regression
guard. Fails on unpatched code with the literal '~/.' output.
88803d4 to
0f816e8
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Symptom
Tool descriptions, log messages, and prompts that embed
display_hermes_home()rendered strings like:in some Docker setups. The
~is the problem.~universally means the current reader's$HOME— that's its only meaning across shells, libraries, and the broader ecosystem. Whoever expands the shorthand — a human running a shell command, an LLM agent placing a file — looks up$HOMEin their own environment and resolves the path against that. They have no way to know it was meant to point atHERMES_HOMEinstead.In LLM-driven workflows that mismatch causes silent miscompilation: the agent, seeing
~/./scripts/in a tool description, reads it as$HOME/scripts/(or worse, pattern-matches it against the conventional~/.hermes/scripts/path) and writes script files there. The cron scheduler, which resolves script paths againstHERMES_HOME/scripts/, then can't find them at execution time.Root cause
The official Docker image sets the unix user's home directory equal to
HERMES_HOME:Dockerfile:21—RUN useradd -u 10000 -m -d /opt/data hermesDockerfile:86—ENV HERMES_HOME=/opt/dataSo inside the agent process,
Path.home()andget_hermes_home()both resolve to/opt/data. The old implementation:evaluates
Path('/opt/data').relative_to(Path('/opt/data'))→Path('.'), producing the shorthand~/.. That gets concatenated into the cronjob tool description as~/./scripts/.The deeper issue: the
~/shorthand is only safe when the reader can resolve it back to the same absolute path. That requiresPath.home()(the reader's$HOME) to be the prefix ofget_hermes_home(). When they're equal, there's nothing left to shorten, and the resulting string is meaningless to anyone whose$HOMEdoesn't happen to match the writer's.Fix
When
home == Path.home()the shorthand cannot produce a useful string, so fall back to the absolute path:In the broken Docker layout this returns
/opt/datainstead of~/.. The cronjob tool description renders as:Unambiguous to any reader.
What's unchanged
HERMES_HOME=~/.hermes) still render~/.hermes.HERMES_HOME=~/.hermes/profiles/coder) still render~/.hermes/profiles/coder.HERMES_HOME=/opt/hermes-custom) were already taking theValueErrorbranch and returning the absolute form — still do.Tests
Two tests in
tests/test_hermes_constants.py:test_default_install_uses_shorthand— the happy path;~/.hermesrendering still works.test_hermes_home_equals_user_home_returns_absolute— the regression. Fails on unpatched code with the literal~/.output, passes on patched code.The profile-install and custom-path-outside-home cases I had earlier exercised the same
relative_to/ValueErrorbranches as #1 — I dropped them as redundant.Test results
Regression test fails on unpatched upstream/main:
Proves the test catches the bug.
With the patch — full
tests/test_hermes_constants.pypasses:Existing callers continue to pass:
End-to-end: verified inside the official Docker container that the cronjob tool description previously rendered as
Relative paths resolve under ~/./scripts/.After this fix it renders asRelative paths resolve under /opt/data/scripts/.Risk
Low. The new branch only fires when
home == Path.home()exactly — a condition that already produced broken output. The existingrelative_toandValueErrorbranches are untouched.