Skip to content

fix(docker): chown ui-tui and node_modules on UID remap so TUI esbuild works (#28851)#33045

Merged
benbarclay merged 1 commit into
mainfrom
salvage-28851-ui-tui-dist
May 27, 2026
Merged

fix(docker): chown ui-tui and node_modules on UID remap so TUI esbuild works (#28851)#33045
benbarclay merged 1 commit into
mainfrom
salvage-28851-ui-tui-dist

Conversation

@benbarclay

Copy link
Copy Markdown
Collaborator

Salvages #28851 (@deas).

Problem

When HERMES_UID remaps the hermes user from the build-time UID 10000 to
another UID (typical use case: matching the host user's UID so bind-mounted
files are owned correctly), the TUI launcher's esbuild step fails:

$ docker run -e HERMES_UID=1000 -e HERMES_GID=1000 nousresearch/hermes-agent --tui

  ✘ [ERROR] Failed to write to output file:
     open /opt/hermes/ui-tui/dist/entry.js: permission denied
  TUI build failed.
  Error: Build failed with 1 error

This is because the Dockerfile's build-time chown -R hermes:hermes on
/opt/hermes/{.venv,ui-tui,node_modules} (line 154) wrote UID 10000, and
stage2-hook.sh only re-chowned .venv on UID remap — leaving the TUI
build trees still owned by the old UID. esbuild needs to rewrite
ui-tui/dist/entry.js on every launch where the source mtime is newer
than dist/ (or when HERMES_TUI_FORCE_BUILD=1 is set), and it runs
as the new hermes UID, which can no longer write to dist/.

Fix

Extend the stage2 re-chown to include the same set as the build-time
Dockerfile chown:

chown -R hermes:hermes \
    "$INSTALL_DIR/.venv" \
    "$INSTALL_DIR/ui-tui" \
    "$INSTALL_DIR/node_modules"

These are the runtime-writable trees under $INSTALL_DIR. Everything else
under /opt/hermes is read-only at runtime so keeping it root-owned is fine.

Sync rule documented in the comment so future Dockerfile chown changes
keep stage2 in step.

Validation

Wrote an isolated TUI UID-remap E2E harness that forces the build path
(HERMES_TUI_FORCE_BUILD=1) and runs docker run --tui with HERMES_UID=1000.

Baseline (origin/main, current behavior)

✗ Bug #28851 reproduced: esbuild EACCES on ui-tui/dist/
  ✘ [ERROR] Failed to write to output file: open /opt/hermes/ui-tui/dist/entry.js: permission denied
✗ TUI launcher reports build failed
✓ stage2 chown path triggered: [stage2] Fixing ownership of /opt/data (targeted) to hermes (1000)
✓ stage2 chown of $INSTALL_DIR build trees succeeded silently

Result: 3 pass, 2 fail — bug reproduces cleanly, container exits rc=1

Salvage (this PR)

✓ No esbuild EACCES on ui-tui/dist/ — fix in place
✓ TUI build did not fail
✓ ui-tui/dist/entry.js present in image after build
✓ Container reached TTY-check stage (expected non-interactive exit point)
✓ stage2 chown path triggered: [stage2] Fixing ownership of /opt/data (targeted) to hermes (1000)
✓ stage2 chown of $INSTALL_DIR build trees succeeded silently

Result: 6 pass, 0 fail — container exits rc=0

Regression checks

Authorship

Original change by @deas in #28851. Their branch targeted
docker/entrypoint.sh (a deprecated shim since the s6-overlay rework
moved the real cont-init logic into docker/stage2-hook.sh);
retargeted the fix accordingly. Preserved attribution via
Co-authored-by:.

Closes #28851.

…d works (#28851)

When HERMES_UID remaps the hermes user from 10000 to another UID
(e.g. matching the host user's UID for bind-mount ergonomics), the TUI
launcher's esbuild step fails:

  ✘ [ERROR] Failed to write to output file:
     open /opt/hermes/ui-tui/dist/entry.js: permission denied
  TUI build failed.

This is because the Dockerfile's build-time `chown -R hermes:hermes` on
`/opt/hermes/{.venv,ui-tui,node_modules}` (line 154) wrote UID 10000,
and stage2-hook.sh only re-chowned `.venv` on UID remap — leaving the
TUI build trees still owned by the old UID.

Extend the stage2 re-chown to include the same set as the build-time
chown: `.venv`, `ui-tui`, `node_modules`. These are the runtime-writable
trees under $INSTALL_DIR; everything else under /opt/hermes is read-only
at runtime so keeping it root-owned is fine.

Original fix targeted docker/entrypoint.sh which is now a deprecated shim;
retargeted to docker/stage2-hook.sh where the .venv chown moved during
the s6-overlay rework.

Co-authored-by: Andreas Steffan <623481+deas@users.noreply.github.com>
@benbarclay benbarclay added the area/docker Docker image, Compose, packaging label May 27, 2026
@github-actions

Copy link
Copy Markdown
Contributor

🔎 Lint report: salvage-28851-ui-tui-dist vs origin/main

ruff

Total: 0 on HEAD, 0 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 0 pre-existing issues carried over.

ty (type checker)

Total: 9400 on HEAD, 9400 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 4973 pre-existing issues carried over.

Diagnostics are surfaced as warnings — this check never fails the build.

@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/tui Terminal UI (ui-tui/ + tui_gateway/) backend/docker Docker container execution labels May 27, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

Salvage of #28851. Related to #27221 (tracking issue), #27243 and #20509 (competing fix PRs for the same chown gap). This PR covers ui-tui/ and node_modules/ but not gateway/ (see #27221 for the full scope).

@benbarclay benbarclay merged commit 22eb4d1 into main May 27, 2026
24 of 25 checks passed
@benbarclay benbarclay deleted the salvage-28851-ui-tui-dist branch May 27, 2026 05:41
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 backend/docker Docker container execution comp/tui Terminal UI (ui-tui/ + tui_gateway/) 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.

2 participants