Skip to content

fix(coding-agent): surface the real spawn error when the daemon cannot launch a worker - #1918

Merged
xeophon merged 3 commits into
mainfrom
snimu/emfile-spawn-error
Aug 31, 2026
Merged

fix(coding-agent): surface the real spawn error when the daemon cannot launch a worker#1918
xeophon merged 3 commits into
mainfrom
snimu/emfile-spawn-error

Conversation

@snimu

@snimu snimu commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

On macOS the daemon inherits the first client's file-descriptor soft limit (default 256). Each detached session keeps a resident worker (+2 daemon fds), so heavy multi-session use eventually makes worker spawn() fail with EMFILE. On that failure Node returns a ChildProcess with pid=undefined and stdio=undefined, and launchWorker dereferenced child.stdio[WORKER_STARTUP_GATE_FD] synchronously:

Session worker <id> process error: spawn .../bin/node EMFILE
Supervisor command create failed: TypeError: Cannot read properties of undefined (reading '3')

The TypeError masked the real error, was sent to the client as the create failure, and crashed the client with a raw Node stack dump. Users saw "after a couple of sessions it can't create a new one" with no clue that killing resident workers (freeing fds) was the fix.

The fix

  • Supervisor: launchWorker awaits a spawnSettled race of the child's spawn vs error events before touching stdio. A failed spawn now rejects the create with the real spawn error, plus - for EMFILE/ENFILE - the resident worker count and a hint (stop unused sessions or raise the open-file limit (ulimit -n)). Other errnos (e.g. ENOENT) keep their message without the fd hint. On success stdio is populated synchronously before the await, so the healthy path is unchanged; failure cleanup rides the existing close/catch path (verified for both the EMFILE shape, stdio=undefined, and the ENOENT shape, live gate stream destroyed).
  • Client: generic create failures deserialize as DaemonSessionCreateError and are handled at the two existing call-site boundaries - one red line, exit 1 - instead of escaping runCli as an unhandled stack dump. Responses with typed errorInfo (e.g. session_already_active) pass through unwrapped, keeping their dedicated handling.

Both failure shapes were probed against real Node on macOS (ulimit -n 64) rather than assumed; the supervisor test's fake child mirrors that verified shape, and the pre-fix test run reproduced the exact production TypeError.

Linear: ENG-5808


Note

Medium Risk
Touches daemon worker launch and CLI error boundaries; behavior change is intentional (fail fast with clear messages) but alters how create failures surface to callers.

Overview
Fixes daemon session create when worker spawn() fails (e.g. EMFILE from too many resident workers): users previously saw a masked TypeError on undefined child.stdio and an unhandled CLI stack dump instead of the real spawn error.

Supervisor (launchWorker) now waits for the child spawn or error event before touching stdio, rejects create with the actual spawn message, and for EMFILE/ENFILE appends resident worker count plus a hint to stop sessions or raise ulimit -n.

Client deserializes untyped create failures as DaemonSessionCreateError (typed errorInfo responses still map to existing errors like SessionAlreadyActiveError). main.ts catches that at interactive and RPC create paths, prints one red error line, and exits 1.

Reviewed by Cursor Bugbot for commit 01d9643. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Fix CLI crash when daemon cannot spawn a worker, surfacing the real error

  • DaemonSupervisor.launchWorker now awaits a spawnSettled promise before accessing child.stdio, so failed spawns (e.g. EMFILE) throw a clear error instead of crashing later on undefined stdio
  • Adds describeWorkerSpawnFailure which appends a resident-worker count and ulimit -n hint for EMFILE/ENFILE errors
  • Introduces DaemonSessionCreateError and deserializeDaemonCreateError so generic daemon create failures are deserialized into a distinct error type that the CLI can handle
  • main.ts catches DaemonSessionCreateError at session creation and prints a single-line error with exit code 1 instead of rethrowing a stack trace
  • Behavioral Change: callers of createDaemonClientConnection now receive DaemonSessionCreateError for untyped create failures instead of a generic Error

Macroscope summarized 01d9643.

snimu added 3 commits August 31, 2026 15:27
…eate errors

When the daemon hits its fd limit, spawn fails with EMFILE and leaves
child.stdio undefined; launchWorker crashed with a raw TypeError that
masked the real error and reached the client as an unhandled throw with
a stack dump.

- daemon-supervisor: race the child spawn/error events so a failed spawn
  deterministically fails the create with the real spawn error, enriched
  with the resident worker count and a ulimit hint for EMFILE/ENFILE.
- CLI: wrap generic daemon create failures in DaemonSessionCreateError
  and handle it at both createDaemonClientConnection call-site boundaries
  as a one-line error with exit code 1.
@xeophon
xeophon merged commit 9f5edc1 into main Aug 31, 2026
23 checks passed
@xeophon
xeophon deleted the snimu/emfile-spawn-error branch August 31, 2026 15:47
PR9000 pushed a commit to PR9000/prime-agent that referenced this pull request Sep 1, 2026
…t launch a worker (PrimeIntellect-ai#1918)

* fix(coding-agent): surface worker spawn failures (EMFILE) as clean create errors

When the daemon hits its fd limit, spawn fails with EMFILE and leaves
child.stdio undefined; launchWorker crashed with a raw TypeError that
masked the real error and reached the client as an unhandled throw with
a stack dump.

- daemon-supervisor: race the child spawn/error events so a failed spawn
  deterministically fails the create with the real spawn error, enriched
  with the resident worker count and a ulimit hint for EMFILE/ENFILE.
- CLI: wrap generic daemon create failures in DaemonSessionCreateError
  and handle it at both createDaemonClientConnection call-site boundaries
  as a one-line error with exit code 1.

* test(coding-agent): assert spawn-failure hint is EMFILE/ENFILE-only (kills errno-unconditional-hint mutant)

* chore(coding-agent): trim comments in the spawn-failure change
ketema added a commit to ketema/prime-agent that referenced this pull request Sep 1, 2026
- Direct session transport between TUI and worker (ENG-5817, PrimeIntellect-ai#1926)
- Event-driven supervisor agent roster with push subscriptions (PrimeIntellect-ai#1897, PrimeIntellect-ai#1900, PrimeIntellect-ai#1895)
- Hardened daemon startup, recovery ownership, and worker launch diagnostics (PrimeIntellect-ai#1929, PrimeIntellect-ai#1918)
- Python REPL runtime single-dump snapshots and bash preview tool (PrimeIntellect-ai#1945, PrimeIntellect-ai#1911)
- Non-blocking RLM subagent deletion and snapshot update suppression (PrimeIntellect-ai#1954, PrimeIntellect-ai#1944)
- Saved catalog loading on Agents View open (PrimeIntellect-ai#1960)
- Advanced Anthropic prompt caching marker across tool results (PrimeIntellect-ai#1927)
- TUI process replacement on update and empty draft eviction (PrimeIntellect-ai#1631, PrimeIntellect-ai#1946, PrimeIntellect-ai#1920)
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.

2 participants