Skip to content

fix(mcp): stop waiting on a daemon record overwritten by a pre-1.0 daemon - #363

Merged
StefanSteiner merged 1 commit into
tableau:mainfrom
StefanSteiner:fix/mcp-legacy-daemon-record-stall
Oct 21, 2026
Merged

StefanSteiner merged 1 commit into
tableau:mainfrom
StefanSteiner:fix/mcp-legacy-daemon-record-stall

Conversation

@StefanSteiner

Copy link
Copy Markdown
Contributor

Summary

  • A pre-1.0 daemon takes no daemon lock and rewrites ~/.hyperdb/daemon.json in its own shape (health_port, no health_endpoint). When it started while a current daemon held the lock, every client saw a held lock beside an unreadable record, concluded the daemon was still starting, and polled the full 20 s SPAWN_TIMEOUT.
  • The local-mode fallback then failed to attach the persistent DB (the daemon's hyperd already held it, error 55006), so each engine retry (warm_up_engine, then the first tool calls) repeated the wait. Observed startup: about 60 s (three consecutive 20 s waits in hyperdb-mcp.log, 4,900 daemon discovery file is malformed lines).
  • In ensure_daemon_in, when the lock is held and the record is a legacy one, the client now asks the lock holder for its own DaemonInfo (identified PING, then STATUS, on the endpoint the state directory implies) and uses it, going through the existing version-takeover check. If the holder does not answer within a 2 s grace (covers a daemon that has the lock but has not bound its endpoint yet) it fails immediately with an error naming the legacy daemon's pid and the file to delete. The legacy record is never removed here, since it belongs to a daemon that is still running.
  • Windows: the pipe name is random per daemon and only recorded in the file, so recovery finds nothing there and it takes the fast-fail path.

Test plan

  • held_lock_with_a_legacy_record_fails_fast_without_spawning (red before the fix: waited the full 30 s timeout)
  • held_lock_with_a_legacy_record_recovers_the_info_from_the_lock_holder (unix; real HealthListener on the well-known endpoint; red before the fix)
  • cargo test -p hyperdb-mcp --lib daemon:: : 88 passed, 0 failed
  • cargo clippy --workspace --all-targets --all-features -- -D warnings clean
  • RUSTDOCFLAGS="-D warnings" cargo doc --no-deps -p hyperdb-mcp clean
  • cargo fmt --all, npx markdownlint-cli2 clean on the changelog
  • CI on macOS/Windows/Linux (the recovery test is cfg(unix))

Per-crate hyperdb-mcp/CHANGELOG.md updated under [Unreleased] → ### Fixed.

…emon

A pre-1.0 daemon takes no daemon lock and rewrites daemon.json in its own
shape. While a current daemon held the lock, clients saw a held lock beside an
unreadable record, assumed the daemon was still starting, and polled the full
20 s spawn timeout. The local-mode fallback then failed to attach the
persistent database (the daemon's hyperd already held it), so each engine
retry repeated the wait and startup took about a minute.

When the lock is held and the record is a legacy one, ask the lock holder for
its own info over the endpoint the state directory implies and use that. If it
does not answer within a 2 s grace, fail at once with an error naming the
pre-1.0 daemon's pid and the file to delete.
@StefanSteiner
StefanSteiner merged commit 264b56d into tableau:main Oct 21, 2026
32 of 33 checks passed
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