Skip to content

Dashboard Chat tab fails with EACCES in Docker image — /opt/hermes/ui-tui is root-owned but dashboard runs as hermes #20500

@jbellsolutions

Description

@jbellsolutions

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.
> 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

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)

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Medium — degraded but workaround existsarea/dockerDocker image, Compose, packagingcomp/tuiTerminal UI (ui-tui/ + tui_gateway/)sweeper:implemented-on-mainSweeper: behavior already present on current maintype/bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions