Skip to content

fix(config): chown ensure_hermes_home dirs to HERMES_UID/GID in Docker (#34107)#34268

Merged
benbarclay merged 1 commit into
NousResearch:mainfrom
Bartok9:fix/ensure-hermes-home-uid-mismatch-34107
Jun 1, 2026
Merged

fix(config): chown ensure_hermes_home dirs to HERMES_UID/GID in Docker (#34107)#34268
benbarclay merged 1 commit into
NousResearch:mainfrom
Bartok9:fix/ensure-hermes-home-uid-mismatch-34107

Conversation

@Bartok9

@Bartok9 Bartok9 commented May 29, 2026

Copy link
Copy Markdown
Contributor

Fixes #34107.

Problem

When Hermes runs in Docker with HERMES_UID=1000 / HERMES_GID=911, the entrypoint chowns the top-level HERMES_HOME once at startup. But subdirectories created at runtime by ensure_hermes_home() — especially for profile namespaces under profiles/<name>/ spawned by kanban workers — were landing as root:root and blocking subsequent uid-mapped worker invocations:

PermissionError: [Errno 13] Permission denied:
  '/opt/data/profiles/charles/logs/curator'
Failed to initialize agent: [Errno 13] Permission denied:
  '/opt/data/profiles/charles/logs/agent.log'

Reporter's workaround was chown -R after every container restart. Not suitable for production.

Fix

Two new helpers and one _secure_dir integration:

def _resolve_hermes_uid_gid() -> tuple[Optional[int], Optional[int]]:
    # Read HERMES_UID / HERMES_GID env vars, parse to ints, handle
    # missing/invalid/whitespace/Windows cases.

def _chown_to_hermes_uid(path) -> None:
    # os.chown(path, uid, gid) with -1 sentinel for missing field.
    # Silently swallow EPERM (not running as root) and AttributeError
    # (Windows where chown doesn't exist) — both are non-fatal.

Hook _chown_to_hermes_uid into _secure_dir so it runs after every directory creation in the home-init path. ensure_hermes_home already calls _secure_dir for every subdir it makes, so all newly-created subdirs (including the profile namespaces created by kanban workers) get the right ownership for free.

Safety properties

Scenario Behavior
HERMES_UID / HERMES_GID unset (non-Docker) No-op
Windows (no os.chown) No-op (AttributeError swallowed)
Non-root process EPERM swallowed (entrypoint will fix on next restart)
Only HERMES_UID set Passes -1 for gid (POSIX 'don't change')
Empty-string env vars Treated as unset
Whitespace-padded values Parsed correctly

Tests

14 new tests in tests/hermes_cli/test_ensure_hermes_home_uid_34107.py:

  • TestResolveHermesUidGid (7) — env-var parsing across all the edge cases above
  • TestChownToHermesUid (5) — chown helper invariants (calls, EPERM swallow, AttributeError swallow, no-op when unset, -1 sentinel)
  • TestSecureDirChown (2) — end-to-end: _secure_dir invokes chown only when env is set
$ pytest tests/hermes_cli/test_ensure_hermes_home_uid_34107.py
13 passed, 1 skipped (Windows-specific) in 0.27s

Verified no regression in the broader config test suite:

$ pytest tests/hermes_cli/test_set_config_value.py tests/hermes_cli/test_config.py
119 passed in 1.56s

Credit to @swarthyplacebo for the precise reproduction + impact analysis.

Co-authored-by: Cursor cursoragent@cursor.com

NousResearch#34107)

Fixes NousResearch#34107. When Hermes runs in Docker with HERMES_UID=1000 /
HERMES_GID=911, the entrypoint chowns the top-level HERMES_HOME once
at startup — but subdirectories created at runtime by
ensure_hermes_home() (especially for profile namespaces under
profiles/<name>/ spawned by kanban workers) were landing as root:root
and blocking subsequent uid-mapped worker invocations with:

  PermissionError: [Errno 13] Permission denied:
    '/opt/data/profiles/charles/logs/curator'

Fix: add _resolve_hermes_uid_gid + _chown_to_hermes_uid helpers that
read the env vars and apply chown after mkdir. Invoke from _secure_dir
which already runs after every directory creation in the home-init path,
so all newly-created subdirs (including the profile namespaces) get the
right ownership.

Safety properties:

- No-op when HERMES_UID/HERMES_GID unset (the dominant non-Docker path)
- No-op on Windows (os.chown doesn't exist; AttributeError swallowed)
- No-op when running as non-root (EPERM swallowed — the entrypoint's
  startup chown -R picks it up on next restart, and in most cases the
  dir was already correctly-owned by the calling user)
- Uses -1 sentinel for missing field so only the set value applies
- Empty-string env vars treated as unset

Adds 14 tests across:
- TestResolveHermesUidGid (7) — env-var parsing
- TestChownToHermesUid (5) — chown helper invariants
- TestSecureDirChown (2) — end-to-end through _secure_dir

Co-authored-by: Cursor <cursoragent@cursor.com>
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/cli CLI entry point, hermes_cli/, setup wizard area/docker Docker image, Compose, packaging labels May 29, 2026
@benbarclay benbarclay merged commit 740fb28 into NousResearch:main Jun 1, 2026
20 checks passed
JoeKowal pushed a commit to JoeKowal/hermes-agent that referenced this pull request Jun 4, 2026
NousResearch#34107) (NousResearch#34268)

Fixes NousResearch#34107. When Hermes runs in Docker with HERMES_UID=1000 /
HERMES_GID=911, the entrypoint chowns the top-level HERMES_HOME once
at startup — but subdirectories created at runtime by
ensure_hermes_home() (especially for profile namespaces under
profiles/<name>/ spawned by kanban workers) were landing as root:root
and blocking subsequent uid-mapped worker invocations with:

  PermissionError: [Errno 13] Permission denied:
    '/opt/data/profiles/charles/logs/curator'

Fix: add _resolve_hermes_uid_gid + _chown_to_hermes_uid helpers that
read the env vars and apply chown after mkdir. Invoke from _secure_dir
which already runs after every directory creation in the home-init path,
so all newly-created subdirs (including the profile namespaces) get the
right ownership.

Safety properties:

- No-op when HERMES_UID/HERMES_GID unset (the dominant non-Docker path)
- No-op on Windows (os.chown doesn't exist; AttributeError swallowed)
- No-op when running as non-root (EPERM swallowed — the entrypoint's
  startup chown -R picks it up on next restart, and in most cases the
  dir was already correctly-owned by the calling user)
- Uses -1 sentinel for missing field so only the set value applies
- Empty-string env vars treated as unset

Adds 14 tests across:
- TestResolveHermesUidGid (7) — env-var parsing
- TestChownToHermesUid (5) — chown helper invariants
- TestSecureDirChown (2) — end-to-end through _secure_dir

Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/docker Docker image, Compose, packaging comp/cli CLI entry point, hermes_cli/, setup wizard P2 Medium — degraded but workaround exists type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ensure_hermes_home() creates root-owned dirs in profile subdirectories when kanban workers are dispatched

3 participants