fix(docker): tee supervised gateway stdout to docker logs#33621
Merged
Conversation
Follow-up to #33583 (the gateway-run-supervised redirect). Before this fix, the supervised gateway's stdout (most visibly the "Hermes Gateway Starting…" rich-console banner) was swallowed by `s6-log` into the rotated file at `${HERMES_HOME}/logs/gateways/<profile>/current` and never reached `docker logs`. Operational signal lived in two places: * **docker logs** — saw stderr (Python `logging` defaults to stderr), so warnings/errors were visible. * **the rotated file** — saw stdout (rich banners, `print()` output, third-party libs that wrote to fd 1). This was surprising for users coming from the pre-s6 image, where `docker run … gateway run` produced a single unified stream in `docker logs`. They'd see partial output, conclude something was broken, and dig around for the missing pieces. Fix: add the `1` s6-log action directive before the file destination so each line is forwarded to s6-log's stdout — which propagates up the s6-supervise pipeline to /init's stdout = container stdout = `docker logs`. The file destination is preserved as a second destination, so the rotated log (with ISO 8601 timestamps) still exists for `hermes logs` and for survival across container restarts. Trade-off considered: timestamps. Putting `T` between `1` and the file destination (not before `1`) means: * docker logs sees raw lines — Python's logging formatter has its own timestamps, and `docker logs --timestamps` adds another layer when desired. No double-stamping in the common reading path. * The persisted file gets s6-log's ISO 8601 timestamp so even output that lacked a Python-logger timestamp (rich banners, third-party raw prints) is correlatable in `current`. Verification: * New unit-test assertion in `test_service_manager.py` locks the `s6-log 1` directive into the rendered run-script. Mutation- tested by reverting to the pre-fix script (no `1`); the assert catches it cleanly. * New docker-harness test `test_supervised_gateway_stdout_reaches_docker_logs` builds the image, runs `docker run … gateway run`, and asserts the unique `⚕` banner glyph reaches `docker logs`. Also verifies the rotated file still contains the banner (no regression on the existing file destination). Mutation-tested end-to-end: built a deliberately-broken image without the `1` directive and the test failed exactly as designed, citing the banner present in `current` but absent from `docker logs`. * `website/docs/user-guide/docker.md` gains a new `:::note Where gateway logs go` admonition documenting both destinations and the audit-log file at `${HERMES_HOME}/logs/container-boot.log`. Existing functionality preserved: every other docker-harness test still passes against the new image. Unit-test sweep across `tests/hermes_cli/` (5561 tests) is green.
Contributor
🔎 Lint report:
|
r266-tech
added a commit
to r266-tech/hermes-agent
that referenced
this pull request
May 28, 2026
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.
Summary
Follow-up to #33583 (the
gateway runauto-supervise redirect).Before this fix, the supervised gateway's stdout was swallowed by
s6-loginto the rotated file at${HERMES_HOME}/logs/gateways/<profile>/currentand never reacheddocker logs. Operational signal lived in two places, depending on which fd a given log line went to:logging(stderr)docker logs✓print(), raw stdout writesdocker logsUsers running
docker run … gateway runagainst the s6 image would see partial output indocker logs(warnings yes, the iconic ⚕ Hermes Gateway Starting banner no), conclude something was broken, and have todocker exec+ tail the file to find the missing pieces.Fix
Add the
1s6-log action directive before the file destination so each line is forwarded to s6-log's stdout — which propagates up through the s6-supervise pipeline to /init's stdout = container stdout =docker logs. The file destination is preserved as a second destination, so the rotated log (with ISO 8601 timestamps) still exists forhermes logsand for survival across container restarts.Trade-off: timestamps
Tis non-sticky in s6-log — it only prefixes lines for the next action directive. PuttingTbetween1and the file (not before1) means:docker logsshows raw lines. Python's logging formatter already has its own timestamps, anddocker logs --timestampsadds another layer when desired. No double-stamping in the common reading path.current.Verification
Unit test
New assertion in
test_service_manager.pylocks thes6-log 1directive into the rendered run-script. Mutation-tested: reverted the source to the pre-fix script (no1); the assert caught it cleanly with a clear failure message naming the missing directive.Docker harness test
New
test_supervised_gateway_stdout_reaches_docker_logs(intests/docker/test_gateway_run_supervised.py):docker run -d <image> gateway run⚕banner glyph (or "Hermes Gateway Starting" text) reachesdocker logsEnd-to-end mutation-tested: built a deliberately-broken image without the
1directive; the test failed exactly as designed, with the diagnostic showing the banner present incurrentbut absent fromdocker logs.Regression sweep
tests/hermes_cli/— 5,561 tests pass, 0 failtests/docker/— 27 tests pass (5 existing intest_gateway_run_supervised.py+ the new one, plus 21 in the other 7 docker test files), 0 failDocs
website/docs/user-guide/docker.mdgains a new:::note Where gateway logs goadmonition documenting both destinations explicitly (the supervised stdout → docker logs, the rotated file →hermes logsreads it, persisted via volume mount) plus the audit log at${HERMES_HOME}/logs/container-boot.log. The existing:::tip Gateway runs supervisedadmonition's claim that "docker logsshows the supervised gateway's output" — which was previously aspirational — is now actually correct.Files
hermes_cli/service_manager.py1insertion) + expanded docstring explaining the routingtests/hermes_cli/test_service_manager.py1directivetests/docker/test_gateway_run_supervised.pywebsite/docs/user-guide/docker.md:::note Where gateway logs goadmonition