fix(focus): probe socket liveness so stale daemon leftovers aren't picked - #5
Merged
Merged
Conversation
…cked detect() in both HerdrFocusProvider and CmuxFocusProvider used fs.access (file-exists) to decide if the multiplexer was running. A crashed/killed daemon leaves its socket file on disk, so exists-based detection mistook the stale leftover for a live server — start() then opened a real connection and threw ECONNREFUSED, which pi surfaced as an extension error on every launch (e.g. plain Ghostty with a dead ~/.config/herdr/ herdr.sock). Inside tmux the bug was masked because auto-detect returns at the TMUX_PANE step and never probes herdr. Fix: - lib/focus/socket.ts: probeSocket(path) opens a real connection and closes it; connect resolves only when a listener is accept()-ing, so stale/missing/non-socket paths reject immediately. Never throws, 300ms ceiling. - herdr.detect() + cmux.detect() use probeSocket instead of access. Path resolution (env/marker/candidates) stays existence-based; only liveness moved to the probe — clean separation. - herdr.start() now swallows connect failure and degrades to always-focused, matching cmux's existing resilience, so an explicit `/cursor provider herdr` against a dead socket won't break session_start either. Tests: socket.test.ts (live/missing/stale/regular-file/close/timeout); herdr + cmux detect tests converted from writeFileSync stubs to real listening sockets, plus stale-leftover → false cases; index.test.ts gains a regression guard that a stale herdr socket falls through to static. Bumps to 0.2.3.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Launching
piin plain Ghostty printed an extension error every time:…while the same launch inside
tmuxwas silent.Root cause
detect()in bothHerdrFocusProviderandCmuxFocusProviderusedfs.access(file-exists) to decide whether the multiplexer was running. A crashed/killed daemon leaves its socket file behind on disk, so exists-based detection mistook the stale leftover for a live server →start()opened a real connection →ECONNREFUSED.Inside tmux the bug was masked:
autoDetectreturns at theTMUX_PANEstep and never reaches the herdr probe.Fix
lib/focus/socket.ts(new) —probeSocket(path): opens a real connection and closes it.connectresolves only when a listener isaccept()-ing; stale/missing/non-socket paths reject immediately. Never throws, 300 ms ceiling.herdr.detect()/cmux.detect()— useprobeSocketinstead ofaccess. Path resolution (env/marker/candidates) stays existence-based; only liveness moved to the probe — clean separation.herdr.start()— swallows connect failure and degrades to always-focused, matching cmux's existing resilience, so an explicit/cursor provider herdragainst a dead socket no longer breakssession_start.Tests
tests/focus/socket.test.ts(new): live / missing / stale-file / regular-file / post-close / timeout.writeFileSyncstubs to real listening sockets, plus stale-leftover →falsecases.index.test.ts: regression guard — stale herdr socket falls through tostatic.start()resilience test (explicit dead socket → no throw, no onChange).pnpm typecheck✅ ·pnpm test:run→ 133 pass / 0 fail (was 122 / 1 failing — the pre-existingno env → staticwas already red on machines with a staleherdr.sock).Semver
Patch (0.2.2 → 0.2.3): bugfix + resilience hardening, no API change.
After merge
Tag
v0.2.3→ CI publishes → bump~/.pi/agent/settings.jsonpin@0.2.2→@0.2.3.