Skip to content

feat(health): add internal HTTP health endpoint for Docker/orchestrators - #217

Open
iagorobo24-hub wants to merge 5 commits into
grinev:mainfrom
iagorobo24-hub:feat/health-endpoint
Open

feat(health): add internal HTTP health endpoint for Docker/orchestrators#217
iagorobo24-hub wants to merge 5 commits into
grinev:mainfrom
iagorobo24-hub:feat/health-endpoint

Conversation

@iagorobo24-hub

@iagorobo24-hub iagorobo24-hub commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Description of changes

Adds an internal HTTP health server (src/health/server.ts) exposing three endpoints on configurable port (default 3100, BOT_HEALTH_PORT, 0 to disable):

  • GET /health/live — Liveness probe (process alive), always 200, no dependency checks
  • GET /health/ready — Readiness probe, 200 if healthy, 503 if degraded (OpenCode down)
  • GET /health — Full health payload: {status, version, uptimeSeconds, checks: {process, opencode}}, always 200 with status field (Docker healthcheck convention)

Integrated into app lifecycle (start-bot-app.ts): starts after settings load, stops on shutdown signals. Used by Docker HEALTHCHECK in docker-compose.yml and by hermes-ops via health.type: http.

Docker healthcheck reads BOT_HEALTH_PORT env var:

  • Default 3100 → checks http://127.0.0.1:3100/health
  • Custom port (e.g. 3200) → checks that port
  • 0 → health server disabled, healthcheck exits 0 (container stays healthy)

Motivation

  • Docker: Current compose has no native healthcheck; hermes-ops reports UNKNOWN because bot doesn't expose health.
  • Observability: Detects process liveness (/live) + OpenCode dependency (/ready, /health) in standard endpoints.
  • Orchestrators: Standard /health/live + /health/ready for Kubernetes/Docker Compose/swarm.

Design choices

  • Binds only to 127.0.0.1 (loopback) — safe with network_mode: host.
  • Uses native fetch (Node 22) — no extra deps (curl/wget not in slim image).
  • Returns degraded (not unhealthy) when OpenCode is down — bot process is alive, can recover automatically.
  • Version/uptime in payload for debugging.
  • checks.process (liveness) + checks.opencode (dependency) — no false "event loop" claim.

How it was tested

  • npm run lint
  • npm run build
  • npm test ✅ (2 pre-existing flaky failures in event-subscription-service.lifecycle unrelated)
  • Local Docker build + run:
    • Default 3100 → healthy
    • Custom BOT_HEALTH_PORT=3200 → healthcheck uses 3200, healthy
    • BOT_HEALTH_PORT=0 → server disabled, healthcheck exits 0, container stays healthy
  • Simulated OpenCode down → /health returns degraded, /health/ready returns 503

Checklist

  • PR title follows Conventional Commits
  • This PR contains one logically complete change
  • Branch is rebased on the latest main
  • I ran npm run lint, npm run build, and npm test
  • Behavior/limitations for Linux/macOS/Windows described (binds 127.0.0.1 only)
  • Docker healthcheck respects BOT_HEALTH_PORT, 0 disables server and passes
  • docker compose config validates (exit 0)

- Add startHealthServerMock/stopHealthServerMock to vi.hoisted
- Mock config.health.port = 0 to disable server in tests
- Mock src/health/server.js imports

Fixes 11 test failures introduced by health endpoint feature.
- src/config.ts: BOT_HEALTH_PORT (default 3100, 0=disable)
- src/app/bootstrap/start-bot-app.ts: start/stop health server integration
- .env.example: document BOT_HEALTH_PORT
- Dockerfile: EXPOSE 3100
- docker-compose.yml: healthcheck + BOT_HEALTH_PORT env + named volume
- tests/health/server.test.ts: 11 tests covering live/ready/health, degraded, port=0, lifecycle
- tests/app/start-bot-app.test.ts: verify startHealthServer/stopHealthServer called
…ove dead code

- /health/live: liveness, 200 always
- /health/ready: readiness, 200 healthy / 503 degraded
- /health: full payload, always 200 with status field
- Remove 'eventLoop' check (was always true), rename to 'process'
- Remove httpCode dead variable and contradictory comments
- Bind 127.0.0.1 only (security)
- Remove trailing garbage (YAL/echo)
- Healthcheck reads BOT_HEALTH_PORT env: 0=disabled/exit 0, else uses that port
- docker compose config validates
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