hermes - ✅(Solved) Fix Dashboard Chat tab fails with EACCES in Docker image — /opt/hermes/ui-tui is root-owned but dashboard runs as hermes [1 pull requests, 1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
NousResearch/hermes-agent#20500Fetched 2026-05-06 06:36:34
View on GitHub
Comments
1
Participants
2
Timeline
6
Reactions
0
Timeline (top)
labeled ×4commented ×1cross-referenced ×1

In the official nousresearch/hermes-agent:latest Docker image (verified on v0.12.0 (2026.4.30)), the dashboard's embedded Chat tab (enabled by hermes dashboard --tui) fails with the banner "Chat unavailable: 1" on first connection.

Root cause: the dashboard runs as the unprivileged hermes user (after the entrypoint drops privileges via gosu), but /opt/hermes/ui-tui/ and its dist/ directories are baked into the image as root:root. When _make_tui_argv() tries to rebuild the TUI bundle on first run, esbuild fails to write to /opt/hermes/ui-tui/packages/hermes-ink/dist/entry-exports.js with EACCES, the build aborts, _make_tui_argv calls sys.exit(1), and web_server.pty_ws reports Chat unavailable: {SystemExit(1)} over the WebSocket.

Error Message

✘ [ERROR] Failed to write to output file:

Bonus: better error surfacing

Root Cause

Root cause: the dashboard runs as the unprivileged hermes user (after the entrypoint drops privileges via gosu), but /opt/hermes/ui-tui/ and its dist/ directories are baked into the image as root:root. When _make_tui_argv() tries to rebuild the TUI bundle on first run, esbuild fails to write to /opt/hermes/ui-tui/packages/hermes-ink/dist/entry-exports.js with EACCES, the build aborts, _make_tui_argv calls sys.exit(1), and web_server.pty_ws reports Chat unavailable: {SystemExit(1)} over the WebSocket.

Fix Action

Workaround

Chown the directory inside the running container:

docker exec -u root <container> chown -R hermes:hermes /opt/hermes/ui-tui

After that the build succeeds ((['/usr/bin/node', '/opt/hermes/ui-tui/dist/entry.js'], ...)), and the Chat tab works on next connect.

This survives docker compose restart but not image rebuild / pull, so users hit it again after every update.

PR fix notes

PR #20509: fix(docker/tui): chown ui-tui to hermes + actionable build error (#20500)

Description (problem / solution / changelog)

Summary

Fixes #20500 — the dashboard's embedded Chat tab fails on first connect in the official Docker image with the unhelpful banner "Chat unavailable: 1".

Root cause (per the issue's reproduction)

Three compounding factors:

  1. Dockerfile runs the TUI build as root: COPY --chown=hermes:hermes . . chowns the source tree, but the RUN cd ui-tui && npm run build step executes as root (no preceding USER directive). The artifacts written into ui-tui/dist/ and ui-tui/packages/hermes-ink/dist/ end up root-owned.
  2. At runtime the entrypoint drops privileges via gosu hermes. When _make_tui_argv decides the bundle is stale and reruns npm run build, esbuild fails with EACCES on the root-owned dist dirs.
  3. _make_tui_argv calls sys.exit(1). web_server.pty_ws catches the resulting SystemExit and renders Chat unavailable: {exc}str(SystemExit(1)) is just \"1\", hence the unhelpful banner.

Fix

Three coordinated changes (each independent, but together close the bug for both new images and remapped-UID deployments):

  1. Dockerfile — chown /opt/hermes/ui-tui to hermes:hermes immediately after npm run build. Survives image rebuilds, so users no longer have to chown manually after every docker compose pull. (Implements suggestion 1 in the issue.)
  2. docker/entrypoint.sh — defensive recursive chown of $INSTALL_DIR/ui-tui while still running as root, before the gosu drop. Handles the HERMES_UID remap case where the Dockerfile's build-time chown is for UID 10000 but the runtime user has a different UID. Silent failure on rootless containers where chown isn't permitted. (Implements suggestion 2.)
  3. hermes_cli/main.py — replace bare sys.exit(1) in _make_tui_argv with raise SystemExit(\"<reason>\") so pty_ws shows an actionable message. EACCES in the build output is detected and surfaced explicitly with the chown remedy and a pointer to issue #20500. (Implements the bonus surfacing improvement.)

Test plan

  • New tests/hermes_cli/test_tui_build_failure_message.py:
    • EACCES in build output → SystemExit with message containing "permission denied" + "#20500"
    • Generic build failure → SystemExit with descriptive message (not bare 1)
  • All 29 tests in tests/hermes_cli/test_tui_npm_install.py and test_tui_resume_flow.py still pass
  • Manual verification (cannot run from this PR): rebuild image, start container with HERMES_DASHBOARD=1, open Chat tab — banner should not appear

Compatibility

  • Dockerfile chown adds ~1s to build time; cheap on the file count in ui-tui/.
  • Entrypoint chown is no-op on already-correctly-owned trees.
  • SystemExit(\"<str>\") returns a non-zero exit code (the str), so launchers that key on returncode != 0 still see a non-zero exit.

Changed files

  • Dockerfile (modified, +19/-1)
  • docker/entrypoint.sh (modified, +12/-0)
  • hermes_cli/main.py (modified, +20/-3)
  • tests/hermes_cli/test_tui_build_failure_message.py (added, +110/-0)

Code Example

gh issue create --repo NousResearch/hermes-agent \
  --title "Dashboard Chat tab fails with EACCES in Docker image — /opt/hermes/ui-tui is root-owned but dashboard runs as hermes" \
  --body-file docs/upstream-issue-chat-tab-eacces.md

---

services:
     hermes:
       image: nousresearch/hermes-agent:latest
       command: ["/bin/bash", "-c", "hermes dashboard --host 0.0.0.0 --port 9119 --no-open --insecure --tui & exec hermes gateway run --accept-hooks"]
       ports: ["127.0.0.1:18789:9119"]
       environment:
         HERMES_INFERENCE_PROVIDER: openrouter
         HERMES_INFERENCE_MODEL: deepseek/deepseek-v4-flash
         OPENROUTER_API_KEY: <key>

---

docker exec -u hermes <container> bash -c '\
  export HOME=/opt/data && \
  /opt/hermes/.venv/bin/python3 -c \
  "from hermes_cli.main import _make_tui_argv, PROJECT_ROOT; \
   print(_make_tui_argv(PROJECT_ROOT / \"ui-tui\", tui_dev=False))"'

---

TUI build failed.
> hermes-tui@0.0.1 build
> npm run build --prefix packages/hermes-ink && tsc -p tsconfig.build.json && ...

[ERROR] Failed to write to output file:
   open /opt/hermes/ui-tui/packages/hermes-ink/dist/entry-exports.js: permission denied

---

docker exec -u root <container> chown -R hermes:hermes /opt/hermes/ui-tui
RAW_BUFFERClick to expand / collapse

Upstream issue draft — Hermes Chat tab EACCES

File this against NousResearch/hermes-agent once reviewed. Run from this directory:

gh issue create --repo NousResearch/hermes-agent \
  --title "Dashboard Chat tab fails with EACCES in Docker image — /opt/hermes/ui-tui is root-owned but dashboard runs as hermes" \
  --body-file docs/upstream-issue-chat-tab-eacces.md

Summary

In the official nousresearch/hermes-agent:latest Docker image (verified on v0.12.0 (2026.4.30)), the dashboard's embedded Chat tab (enabled by hermes dashboard --tui) fails with the banner "Chat unavailable: 1" on first connection.

Root cause: the dashboard runs as the unprivileged hermes user (after the entrypoint drops privileges via gosu), but /opt/hermes/ui-tui/ and its dist/ directories are baked into the image as root:root. When _make_tui_argv() tries to rebuild the TUI bundle on first run, esbuild fails to write to /opt/hermes/ui-tui/packages/hermes-ink/dist/entry-exports.js with EACCES, the build aborts, _make_tui_argv calls sys.exit(1), and web_server.pty_ws reports Chat unavailable: {SystemExit(1)} over the WebSocket.

Reproduction

  1. Pull and run the official image with the dashboard in --tui mode:
    services:
      hermes:
        image: nousresearch/hermes-agent:latest
        command: ["/bin/bash", "-c", "hermes dashboard --host 0.0.0.0 --port 9119 --no-open --insecure --tui & exec hermes gateway run --accept-hooks"]
        ports: ["127.0.0.1:18789:9119"]
        environment:
          HERMES_INFERENCE_PROVIDER: openrouter
          HERMES_INFERENCE_MODEL: deepseek/deepseek-v4-flash
          OPENROUTER_API_KEY: <key>
  2. Open http://localhost:18789 → Chat tab.
  3. Banner shows: Chat unavailable: 1

Confirmed root cause

Run _make_tui_argv directly as the hermes user:

docker exec -u hermes <container> bash -c '\
  export HOME=/opt/data && \
  /opt/hermes/.venv/bin/python3 -c \
  "from hermes_cli.main import _make_tui_argv, PROJECT_ROOT; \
   print(_make_tui_argv(PROJECT_ROOT / \"ui-tui\", tui_dev=False))"'

Output (abridged):

TUI build failed.
> [email protected] build
> npm run build --prefix packages/hermes-ink && tsc -p tsconfig.build.json && ...

✘ [ERROR] Failed to write to output file:
   open /opt/hermes/ui-tui/packages/hermes-ink/dist/entry-exports.js: permission denied

ls -la /opt/hermes/ui-tui/dist /opt/hermes/ui-tui/packages/hermes-ink/dist shows both directories owned by root:root in the shipped image, while the running process is uid=hermes.

Workaround

Chown the directory inside the running container:

docker exec -u root <container> chown -R hermes:hermes /opt/hermes/ui-tui

After that the build succeeds ((['/usr/bin/node', '/opt/hermes/ui-tui/dist/entry.js'], ...)), and the Chat tab works on next connect.

This survives docker compose restart but not image rebuild / pull, so users hit it again after every update.

Suggested fixes (any one is sufficient)

  1. Dockerfile: RUN chown -R hermes:hermes /opt/hermes/ui-tui after the build stage that produces dist/.
  2. Entrypoint (/opt/hermes/docker/entrypoint.sh): chown /opt/hermes/ui-tui alongside the existing $HERMES_HOME chown (it already runs as root before the gosu drop).
  3. Skip rebuild when dist is current: tighten _tui_need_npm_install / _tui_build_needed so a fresh image with a complete prebuilt dist/ doesn't trigger a rebuild attempt at all. The entry.js is already present and runnable; the rebuild is only needed in dev mode.

Bonus: better error surfacing

pty_ws in hermes_cli/web_server.py catches SystemExit and renders Chat unavailable: {exc} — which becomes the unhelpful Chat unavailable: 1. Surfacing the underlying npm/esbuild stderr (or at least a hint like "TUI build failed; see container logs") would have shaved hours off debugging.

Environment

  • Image: nousresearch/hermes-agent:latest (v0.12.0 (2026.4.30))
  • Host: Debian 12 / Docker 26.x on a DigitalOcean droplet
  • Compose: docker compose v2
  • Started via hermes dashboard --tui in foreground+background pattern (dashboard bg, hermes gateway run fg)

Related code paths

  • _DASHBOARD_EMBEDDED_CHAT_ENABLED gates /api/pty (works correctly)
  • _make_tui_argv in hermes_cli/main.py (the failing path)
  • pty_ws in hermes_cli/web_server.py (catches SystemExit and returns the unhelpful : 1 banner)

extent analysis

TL;DR

The most likely fix is to change the ownership of the /opt/hermes/ui-tui directory to the hermes user in the Docker image.

Guidance

  • The issue is caused by the hermes user not having write permission to the /opt/hermes/ui-tui directory, which is owned by root.
  • To fix this, you can add a RUN chown -R hermes:hermes /opt/hermes/ui-tui command to the Dockerfile after the build stage that produces the dist/ directory.
  • Alternatively, you can modify the entrypoint script to chown the /opt/hermes/ui-tui directory before dropping privileges.
  • You can also consider skipping the rebuild of the TUI bundle when the dist/ directory is already up-to-date.

Example

# In the Dockerfile
RUN chown -R hermes:hermes /opt/hermes/ui-tui

Notes

  • The provided workaround of running chown -R hermes:hermes /opt/hermes/ui-tui inside the container is not a permanent solution, as it will be lost after the container is restarted or rebuilt.
  • The suggested fixes should be applied to the Docker image build process to ensure that the issue is resolved permanently.

Recommendation

Apply the workaround by adding the RUN chown -R hermes:hermes /opt/hermes/ui-tui command to the Dockerfile, as it is a straightforward and effective solution to the issue.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING