Summary
On macOS, the agent's entire Node.js event loop freezes for several minutes (observed ~5–7 min) with no output — it appears completely hung — then self-recovers and the request completes normally. Stack sampling during the freeze shows the main thread blocked in uv__fsevents_close on libuv's internal FSEvents-thread handshake semaphore, while libuv's CFRunLoop/FSEvents thread is alive and idle. This indicates a lost-wakeup / handshake race in libuv's macOS FSEvents close path. It occurs only with file-watching (indexing) enabled and correlated strongly with connecting a corporate VPN; it reproduced across multiple workspaces simultaneously.
Environment
- auggie: 0.28.0 (commit 63537d73)
- Node.js: v26.0.0 (bundled libuv 1.x; libnode.147)
- macOS: 26.5 (build 25F71)
- CPU: Apple Silicon (arm64)
- Invocation:
auggie --workspace-root=<workspace> --allow-indexing --acp
- Workspaces: large, on local APFS volumes (NOT network mounts)
Symptoms
- Agent stops streaming entirely mid-request; appears "still responding" with zero progress.
- ~0% CPU during the freeze.
- Freeze lasted several minutes, then the request completed normally (stop_reason=end_turn).
- Occurred on 2+ workspaces at the same time, shortly after connecting a VPN.
Diagnosis (macOS sample)
Main thread during the freeze (≈100% of samples over multi-second windows):
node::SpinEventLoopInternal(node::Environment*)
uv_run
uv__fsevents_close (libuv.1.0.0.dylib)
<uv_sem_wait>
semaphore_wait_trap (libsystem_kernel.dylib)
libuv CFRunLoop / FSEvents thread at the same time — alive and healthy, parked on its mach port, occasionally still delivering FSEvents callbacks:
CFRunLoopRun
_CFRunLoopRunSpecificWithOptions
__CFRunLoopRun
__CFRunLoopServiceMachPort
mach_msg -> mach_msg2_trap
(occasionally)
__CFRunLoopDoSource1
FSEventsClientProcessMessageCallback
FSEventsD2F_server -> _Xcallback_rpc -> implementation_callback_rpc
uv__fsevents_event_cb (libuv.1.0.0.dylib)
After recovery, the main thread returns to the normal loop:
node::SpinEventLoopInternal -> uv_run -> uv__io_poll -> kevent
Interpretation
On macOS, uv__fsevents_close() hands stream teardown to libuv's dedicated CFRunLoop thread and blocks on a semaphore until that thread acknowledges. Here the main thread is stuck on that semaphore while the CFRunLoop thread is alive but idle in mach_msg — i.e. it is not (or no longer) processing the pending close signal, so the acknowledging post is delayed for minutes. This looks like a lost-wakeup / ordering race in the fsevents close handshake (uv__cf_loop_signal / signal_source / the sync semaphore), rather than the CFRunLoop thread being blocked on real work.
Likely trigger
- Requires active FSEvents watchers (
--allow-indexing). With watchers disabled the path is never taken.
- Strongly correlated with connecting a corporate VPN. Plausible mechanism: VPN connect produces volume/mount//Volumes changes that FSEvents delivers as events, prompting libuv to reschedule/close streams in a burst — hitting the close-handshake race. The CFRunLoop thread is NOT blocked on any network/configd/DNS call, so the VPN's network stack is not directly involved.
Reproduction (observed; not yet minimized)
- macOS arm64, Node v26, with one or more recursive FSEvents watchers active on a large local workspace.
- Trigger a burst of FSEvents activity (connect a VPN that alters mounted volumes, or rapidly create/close many watchers).
- Observe the main thread freeze in
uv__fsevents_close for minutes, then recover.
A minimal repro would likely be a Node script that creates and rapidly closes many recursive fs.watch watchers on macOS while volume-change events fire.
Impact
Total event-loop freeze: the agent appears hung for minutes and all in-flight work stalls. From a host application's perspective it is indistinguishable from a permanent deadlock until it self-heals.
Workaround
Run without --allow-indexing for affected workspaces (removes FSEvents watchers, avoiding the close path entirely).
Possibly related
- Node.js / libuv macOS fsevents reports of
uv__fsevents_close semaphore handshakes hanging under high watcher churn.
Summary
On macOS, the agent's entire Node.js event loop freezes for several minutes (observed ~5–7 min) with no output — it appears completely hung — then self-recovers and the request completes normally. Stack sampling during the freeze shows the main thread blocked in
uv__fsevents_closeon libuv's internal FSEvents-thread handshake semaphore, while libuv's CFRunLoop/FSEvents thread is alive and idle. This indicates a lost-wakeup / handshake race in libuv's macOS FSEvents close path. It occurs only with file-watching (indexing) enabled and correlated strongly with connecting a corporate VPN; it reproduced across multiple workspaces simultaneously.Environment
auggie --workspace-root=<workspace> --allow-indexing --acpSymptoms
Diagnosis (macOS
sample)Main thread during the freeze (≈100% of samples over multi-second windows):
libuv CFRunLoop / FSEvents thread at the same time — alive and healthy, parked on its mach port, occasionally still delivering FSEvents callbacks:
After recovery, the main thread returns to the normal loop:
Interpretation
On macOS,
uv__fsevents_close()hands stream teardown to libuv's dedicated CFRunLoop thread and blocks on a semaphore until that thread acknowledges. Here the main thread is stuck on that semaphore while the CFRunLoop thread is alive but idle inmach_msg— i.e. it is not (or no longer) processing the pending close signal, so the acknowledging post is delayed for minutes. This looks like a lost-wakeup / ordering race in the fsevents close handshake (uv__cf_loop_signal/signal_source/ the sync semaphore), rather than the CFRunLoop thread being blocked on real work.Likely trigger
--allow-indexing). With watchers disabled the path is never taken.Reproduction (observed; not yet minimized)
uv__fsevents_closefor minutes, then recover.A minimal repro would likely be a Node script that creates and rapidly closes many recursive
fs.watchwatchers on macOS while volume-change events fire.Impact
Total event-loop freeze: the agent appears hung for minutes and all in-flight work stalls. From a host application's perspective it is indistinguishable from a permanent deadlock until it self-heals.
Workaround
Run without
--allow-indexingfor affected workspaces (removes FSEvents watchers, avoiding the close path entirely).Possibly related
uv__fsevents_closesemaphore handshakes hanging under high watcher churn.