-
Notifications
You must be signed in to change notification settings - Fork 268
Remote session spawn timeout due to macOS background process throttling #166
Description
Problem
When starting a new session remotely (from the Happy Coder mobile app) while the Mac has no foreground activity (lid closed / screen locked), the spawned happy claude child processes get throttled by macOS and fail to send the session webhook within the 15-second timeout.
Reproduction Steps
- Start the daemon on macOS (
happy daemon start-sync) - Close the laptop lid or lock the screen (keep the machine awake via caffeinate/daemon)
- From the Happy Coder mobile app, try to start a new session on that machine
- The spawn request times out with
[RPC] [ERROR] Error handling request {"error":{}} - Retry multiple times — each retry spawns another zombie process
Observed Behavior
From the daemon log:
[11:51:01] [SPAWN HAPPY CLI] Spawning: happy claude --happy-starting-mode remote --started-by daemon
[11:51:01] [DAEMON RUN] Spawned process with PID 90269
[11:51:01] [DAEMON RUN] Waiting for session webhook for PID 90269
[11:51:16] [DAEMON RUN] Session webhook timeout for PID 90269
[11:51:16] [RPC] [ERROR] Error handling request {"error":{}}
This happened 5 times between 11:51-11:55. All 5 processes woke up simultaneously at 12:28:21 (37 minutes later), confirming macOS was throttling them:
# All 5 child process logs start at the exact same second:
[12:28:21.575] Starting happy CLI with args: ... claude --happy-starting-mode remote --started-by daemon
[12:28:21.577] Starting happy CLI with args: ... claude --happy-starting-mode remote --started-by daemon
[12:28:21.575] Starting happy CLI with args: ... claude --happy-starting-mode remote --started-by daemon
...
Later spawns at 12:59 (when the machine was actively used) succeeded within 3 seconds.
Root Cause
macOS aggressively throttles background processes that:
- Have no TTY (shown as
??inpsoutput) - Have no direct user interaction context
- Are spawned by a background daemon
The daemon itself stays alive (it has caffeinate and was spawned from a terminal), but its child processes inherit a lower scheduling priority.
Impact
- Zombie
happy claudeprocesses accumulate (each retry spawns a new one) - These zombies consume memory (~34MB each) and potentially API connections
- The mobile app shows an error, and the user has no way to know the sessions will eventually start
- Manual cleanup (
killzombie PIDs) is required
Suggested Fixes
-
Wrap child process spawn with
caffeinate -ito prevent macOS from throttling the child:// Instead of spawning `happy claude ...` directly: spawn('caffeinate', ['-i', 'happy', 'claude', '--happy-starting-mode', 'remote', ...])
-
Increase the webhook timeout (e.g., 60s or 120s) for remote-spawned sessions, since macOS scheduling delays can far exceed 15s
-
Deduplicate spawn requests — if a spawn for the same directory is already pending/in-progress, don't spawn another process
-
Kill timed-out child processes — if the webhook times out, send SIGTERM to the spawned PID to prevent zombie accumulation
Environment
- happy-coder: 0.13.0
- Claude Code: 2.1.39
- Node.js: v24.4.1
- macOS: Darwin 24.6.0 (arm64, Apple Silicon)
- Machine: MacBook Pro