Skip to content

fix(daemon): prevent Hermes restart churn and restore missing stdin - #4658

Closed
AimLike0 wants to merge 2 commits into
vectorize-io:mainfrom
AimLike0:fix/hermes-generated-port-drift
Closed

AimLike0 wants to merge 2 commits into
vectorize-io:mainfrom
AimLike0:fix/hermes-generated-port-drift

Conversation

@AimLike0

@AimLike0 AimLike0 commented Sep 23, 2026 •

Copy link
Copy Markdown

Summary

Two separate commits address embedded daemon reliability:

1. Avoid restarts from generated profile metadata

hindsight-embed persists the resolved listener port as HINDSIGHT_API_PORT in the profile .env. Hermes compares that file with _build_embedded_profile_env(), which does not emit the assigned port. The extra key makes subsequent startup look like configuration drift, triggering a profile rewrite and daemon stop.

Ignore only HINDSIGHT_API_PORT in the in-memory comparison. Leave the profile file untouched when all managed settings match. Genuine model/provider/endpoint/key/idle-timeout changes still rewrite and restart; the existing unsafe-rewrite guard is unchanged.

This targets the maintained integration here rather than restoring the removed bundled provider in Hermes Agent. It is independent of #4655 and does not change dependency versions.

2. Restore missing or closed stdin in the daemon child

_redirect_stdio_to_log() assumes sys.stdin.fileno() is usable. Detached launchers can leave stdin unset or fd 0 closed, producing AttributeError/ValueError during startup. When opening /dev/null reuses fd 0, the old context manager also closes it again, allowing the write-only log to reuse fd 0.

Restore descriptor 0 directly from /dev/null, leave it open if os.open returns 0, and recreate the Python stream when missing/closed. Both branches leave fd 0 inheritable. stdout/stderr still redirect to the daemon log.

Related: #4464 fixes the embed launcher by explicitly supplying stdin=DEVNULL. This commit repairs the API child redirection itself and does not copy or replace that launcher change. It also handles closed Python streams and descriptor reuse.

Verification

  • Port regression on the pre-fix code: 1 failed, 7 passed. The added-port case incorrectly called stop('test-daemon').
  • Fixed Hermes integration: 29 passed with uv run --frozen pytest tests -q in hindsight-integrations/hermes.
  • Stdin regression on the pre-fix code: 4 failed, 1 passed (normal stdin passes; missing stream, closed fd, closed stream, and missing stream plus closed fd fail).
  • Fixed API daemon tests: 8 passed, including five disposable subprocess cases plus the three existing test_daemonize.py tests.
  • Subprocess cases verify Python stdin and raw fd 0 both return EOF, fd 0 is inheritable, and Python/raw stdout/stderr reach the log. They use temporary HOME/cwd and an explicit log-path override; descriptor operations and daemon-child dispatch are real. No live service or credentials are touched.
  • API tests run in a minimal isolated environment with --noconftest to avoid unrelated database/ML fixtures; this is focused validation, not the full API suite:
PYTHONPATH=hindsight-api-slim uv run --no-project \
  --with pytest --with pytest-asyncio --with python-dotenv \
  --with pydantic --with sqlalchemy python -m pytest \
  --noconftest -o addopts='' \
  hindsight-api-slim/tests/test_daemon_stdio.py \
  hindsight-api-slim/tests/test_daemonize.py -q
  • Scoped Ruff check/format checks and git diff --check pass.
  • Full monorepo lint was attempted with UV_OFFLINE=1 ./scripts/hooks/lint.sh, but stopped during dependency sync because the cache lacks rignore==0.7.6. Full monorepo lint, the full API suite, and live-daemon integration were not run for this branch.

@AimLike0 AimLike0 changed the title fix(hermes): avoid daemon restarts from generated profile port fix(daemon): prevent Hermes restart churn and restore missing stdin Sep 23, 2026
@AimLike0 AimLike0 closed this Sep 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant