Skip to content

fix(cli): fail fast on admin-port conflict instead of polling 10s - #8

Open
godspede wants to merge 1 commit into
mainfrom
fix/ensure-daemon-port-conflict-fast-fail
Open

godspede wants to merge 1 commit into
mainfrom
fix/ensure-daemon-port-conflict-fast-fail

Conversation

@godspede

@godspede godspede commented Sep 2, 2026

Copy link
Copy Markdown
Owner

Summary

EnsureDaemon decides whether a daemon is already running by reading
<data_dir>/daemon.lock. When that lock is absent or stale, but the
config's admin_port is already held by a different running
ghosthost instance (e.g. one started against a different
--config/data_dir, sharing the same default ports 8750/8751), the
CLI would spawn a duplicate daemon that can only lose the bind race
and die before ever writing a lockfile. The CLI then polls the full
hardcoded 10-second readiness window before giving up with the generic
daemon unreachable -- a message indistinguishable from a genuinely
dead daemon, even though a live daemon was answering on that port the
whole time.

This is the root cause of a live incident where ghosthost --json list reliably took ~10.07s to report daemon unreachable, even
though a raw TCP connect and curl against the same admin port
answered in ~13ms: the CLI was resolving its config from the invoking
user's default config path (no --config flag passed), which pointed
at a different data_dir than the one the actually-running daemon
(started via --config /etc/...) was using.

Change

EnsureDaemon now does a short direct TCP probe of 127.0.0.1:<admin_port>
before spawning, whenever the lockfile read fails. If something is
already listening there, it fails immediately with a message naming
the real, actionable condition instead of spawning a doomed duplicate
and waiting out the full 10s.

Test plan

  • go build ./...
  • go vet ./...
  • go test ./... (all packages green)
  • New regression test TestEnsureDaemon_PortInUseWithoutLock_FailsFastInsteadOfPolling:
    binds a real listener, calls EnsureDaemon against an empty
    data_dir with that port, and asserts the call fails in well
    under the old 10s window with an "already in use" message.
  • Existing TestEnsureDaemon_RefusesToSpawnTestBinary still passes with
    the new adminPort parameter (passed as 0, preserving prior
    no-conflict-check behavior for that path).

EnsureDaemon reads dataDir/daemon.lock to find a live daemon. When that
lock is absent or stale but the config's admin_port is already held by
a DIFFERENT ghosthost instance (e.g. one started against a different
--config/data_dir, sharing the same default ports), EnsureDaemon would
spawn a duplicate daemon that can only lose the bind race and die
before writing a lockfile -- then poll the full hardcoded 10s window
before reporting the generic "daemon unreachable", indistinguishable
from a genuinely dead daemon.

Detect the port conflict with a direct TCP probe before spawning, and
report it immediately with the actionable cause instead.
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