fix(status): poll pidfile registry as fallback for missed fs.watch events#62
Open
raphapizzi wants to merge 1 commit into
Open
fix(status): poll pidfile registry as fallback for missed fs.watch events#62raphapizzi wants to merge 1 commit into
raphapizzi wants to merge 1 commit into
Conversation
…ents fs.watch on macOS (FSEvents) is known to coalesce or drop notifications for in-place file rewrites — the exact pattern Claude Code uses when transitioning its pidfile from `status: "busy"` to `status: "waiting", waitingFor: "approve <Tool>"`. When the registry misses that transition, the entire session-status pipeline stays stuck: - `mapPidfileStatus` keeps returning `executingTool` (green pulsing dot) - `attentionService.ingestSessionUpdate` never sees `awaitingApproval`, so the attention bell never fires and no notification is created - The user is left staring at a "Do you want to proceed?" prompt with no indication from the UI that their attention is required Add a 2s polling rescan as a safety net. fs.watch is still the low-latency primary path; polling closes the gap when an event is dropped, guaranteeing eventual consistency. Optimizations: - `scanAll` now tracks whether anything changed and only calls `notify()` when changes are observed (or on the very first scan, to seed subscribers), keeping steady-state polling free of downstream parseSession work. - `refreshFile` returns a boolean reflecting whether the snapshot changed, so `scanAll` can aggregate without duplicating shallow-equal logic. - The poll timer is `.unref()`'d so it never holds the event loop open in CLI/test contexts. Tests: - New: polling fallback catches a `busy → waiting` transition when fs.watch is forcibly silenced (regression guard). - New: polling fallback stays quiet in steady state (subscriber not spammed every 2s when nothing has changed). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.
Summary
Fixes #61 — a Claude Code session waiting on an approval prompt renders with the green pulsing "Running Tool" dot and never fires the attention bell.
ClaudePidfileRegistrywas 100% event-driven on a singlefs.watch()watcher with no polling fallback. On macOS,fs.watch(FSEvents) is well known to coalesce or drop notifications for in-place rewrites of small files — exactly how Claude Code mutates its pidfile fromstatus: "busy"tostatus: "waiting", waitingFor: "approve <Tool>". When that event is dropped, the in-memory snapshot stays stale forever (until the next subscriber-driven re-scan or app restart), and the entire status / attention pipeline downstream of it (mapPidfileStatus,attentionService.ingestSessionUpdate,StatusDot) silently acts on the wrong state.What changed
src/main/services/providers/claudePidfileRegistry.ts:fs.watch. The watcher remains the low-latency primary path; polling guarantees eventual consistency.scanAllnow tracks whether anything actually changed and only callsnotify()when changes are observed (or on the very first scan, to seed subscribers) — so steady-state polling does not spam downstream consumers.refreshFilereturns a boolean indicating whether the snapshot changed, soscanAllcan aggregate without duplicating shallow-equal logic.setIntervalis.unref()'d to keep CLI/test contexts from being held open.tests/unit/claude-pidfile-registry.test.ts:busy → waitingtransition whenfs.watchis forcibly silenced — this is the direct regression guard for [Bug]: Status dot stuck on green "Running Tool" while session is actually waiting for approval (no notification) #61.Behavior
fs.watchdrops the event): ≤ 2s (was: never observed until next subscribe / app restart).fs.watchfires): unchanged.readdir+ N smallreadFiles every 2s. Existing snapshots useshallowEqualSnapshotdedup; no work propagates downstream when nothing changed.Test plan
node node_modules/vitest/dist/cli.js run tests/unit/claude-pidfile-registry.test.ts— 10/10 pass (8 existing + 2 new)node node_modules/vitest/dist/cli.js run— 330/330 passtsc --noEmit -p tsconfig.node.json— clean🤖 Generated with Claude Code