Bug Description
The standalone hindsight-api CLI does not fail fast on a port conflict. When the target port is already occupied, it performs the entire expensive startup — starts the embedded PostgreSQL, loads the embedding and reranker models, verifies the LLM connection, and runs database migrations — and only afterwards does uvicorn attempt to bind the socket and fail with [Errno 98] address already in use.
Under a process supervisor with Restart=always (systemd, docker restart policy, etc.) this becomes a restart storm: each ~11s cycle does a full init + graceful shutdown, then restarts ~5s later, wasting CPU/memory and — because the embedded PostgreSQL uses port=auto — starting a fresh PG instance on an incrementing port each cycle, which can leave orphan postgres processes behind.
Environment
- hindsight-all 0.9.2 (
hindsight-api standalone entry point)
- Linux, systemd user service
Restart=always, ExecStart=hindsight-api --host 127.0.0.1 --port 9177
- The port is held by another Hindsight daemon (e.g. one spawned via
HindsightEmbedded)
Steps to Reproduce
- Start any listener on
127.0.0.1:9177 (e.g. a second Hindsight daemon).
- Run
hindsight-api --host 127.0.0.1 --port 9177.
Expected Behavior
The CLI should check port availability at the very start (before instantiating MemoryEngine, starting the embedded PG, loading models, or running migrations) and exit immediately with a clear error when the port is unavailable.
Actual Behavior
Full initialization runs first, then the bind fails:
... Starting Hindsight API...
INFO: Waiting for application startup.
... Embeddings: initializing local provider with model BAAI/bge-small-en-v1.5
... Reranker: initializing local provider with model cross-encoder/ms-marco-MiniLM-L-6-v2
... Starting embedded PostgreSQL (name=hindsight, port=auto)...
... PostgreSQL started: postgresql://hindsight:***@127.0.0.1:5433/hindsight
... Running database migrations...
... Database migrations completed successfully for schema 'public'
... Memory system initialized (pool and task backend started)
INFO: Application startup complete.
ERROR: [Errno 98] error while attempting to bind on address ('127.0.0.1', 9177): address already in use
INFO: Waiting for application shutdown.
... Stopping pg0...
Root cause
hindsight_api/server.py instantiates MemoryEngine(...) and create_app(...) at module import time; main.py only calls uvicorn.run() afterwards, and the socket bind happens after the app (and its lifespan startup) has fully initialized. There is no port-availability pre-flight check on the standalone path.
Contrast with embedded mode
The embedded path (hindsight_embed/daemon_embed_manager.py) already guards against this: _clear_port() / _is_port_in_use() run before spawning a daemon (handling healthy-reuse / stale-reclaim / foreign-refuse — see #843, #3520, #3100). The standalone hindsight-api CLI has no equivalent pre-flight check.
Suggested fix
Perform the same loopback port check (_is_port_in_use) at the top of the standalone CLI startup (before MemoryEngine / migrations), and exit with a clear, actionable error when the port is occupied.
Bug Description
The standalone
hindsight-apiCLI does not fail fast on a port conflict. When the target port is already occupied, it performs the entire expensive startup — starts the embedded PostgreSQL, loads the embedding and reranker models, verifies the LLM connection, and runs database migrations — and only afterwards does uvicorn attempt to bind the socket and fail with[Errno 98] address already in use.Under a process supervisor with
Restart=always(systemd, docker restart policy, etc.) this becomes a restart storm: each ~11s cycle does a full init + graceful shutdown, then restarts ~5s later, wasting CPU/memory and — because the embedded PostgreSQL usesport=auto— starting a fresh PG instance on an incrementing port each cycle, which can leave orphan postgres processes behind.Environment
hindsight-apistandalone entry point)Restart=always,ExecStart=hindsight-api --host 127.0.0.1 --port 9177HindsightEmbedded)Steps to Reproduce
127.0.0.1:9177(e.g. a second Hindsight daemon).hindsight-api --host 127.0.0.1 --port 9177.Expected Behavior
The CLI should check port availability at the very start (before instantiating
MemoryEngine, starting the embedded PG, loading models, or running migrations) and exit immediately with a clear error when the port is unavailable.Actual Behavior
Full initialization runs first, then the bind fails:
Root cause
hindsight_api/server.pyinstantiatesMemoryEngine(...)andcreate_app(...)at module import time;main.pyonly callsuvicorn.run()afterwards, and the socket bind happens after the app (and its lifespan startup) has fully initialized. There is no port-availability pre-flight check on the standalone path.Contrast with embedded mode
The embedded path (
hindsight_embed/daemon_embed_manager.py) already guards against this:_clear_port()/_is_port_in_use()run before spawning a daemon (handling healthy-reuse / stale-reclaim / foreign-refuse — see #843, #3520, #3100). The standalonehindsight-apiCLI has no equivalent pre-flight check.Suggested fix
Perform the same loopback port check (
_is_port_in_use) at the top of the standalone CLI startup (beforeMemoryEngine/ migrations), and exit with a clear, actionable error when the port is occupied.