fix(pty-daemon): release macOS PTY master descriptors
Summary
- add idempotent master-fd disposal for native node-pty adapters and inherited/adopted PTYs
- dispose owned descriptors on natural exit, explicit close, failed or partial open/adoption, and normal daemon shutdown
- preserve successful handoff ownership by skipping predecessor signaling and disposal after the successor acknowledges adoption
- keep TreeKiller escalation and socket backpressure behavior intact
Root cause
The standalone pty-daemon removed exited sessions from its store but did not have an explicit descriptor-ownership teardown contract. The host-service reaper cannot close fds held by the detached daemon process, so master descriptors could accumulate across long-lived, high-churn workloads until macOS exhausted kern.tty.ptmx_max.
Implementation
Pty.dispose() now releases the process-owned master descriptor exactly once. Native PTYs destroy node-pty's read/write streams, while adopted PTYs destroy their inherited tty.ReadStream. Close paths signal through the existing TreeKiller before disposal, and its escalation chain continues independently so detached/background descendants are still reaped.
Failed native construction and later partial-open setup kill and dispose the newly created PTY. Partial successor adoption closes only the successor's inherited copies without signaling shells that remain owned by the predecessor. Explicitly closed sessions are immediately excluded from fd handoff while their eventual exit notification still completes normal store cleanup.
Successful handoff still calls close({ killSessions: false }), which neither kills nor disposes predecessor adapters.
The package README now documents the ownership rule and lifecycle coverage.
Validation
bun run test— 66 passedbun run build:daemonbun run test:integration— 56 passed, including control-plane, handoff, flow-control, TreeKiller, signal recovery, and new real-fd lifecycle suites- 128-cycle server churn with real OS descriptors
- 128-cycle real node-pty master-fd churn plus native/adopted idempotency checks
bun run typecheck— repo-wide, 35 tasks passedbun run lint:fixbun run lint
Closes #5305
Summary by cubic
Fixes PTY master-fd leaks by disposing descriptors on teardown across pty-daemon. Also prevents hidden macOS /dev/ptmx leaks by pinning node-pty to a fixed version.
-
Bug Fixes
- Added
dispose()toPty(idempotent) for nativenode-ptyand adopted PTYs. - Dispose on natural exit, explicit close (kill then dispose), failed/partial spawn or adoption, and daemon shutdown; skip after successful handoff.
- Hardened open/adoption rollback: close only the successor’s inherited copies on failure.
- Mark sessions exited on close and ignore stale exit events so recycled ids aren’t deleted.
- Added
-
Dependencies
- Pin
node-ptyto1.2.0-beta.14to fix hidden macOS/dev/ptmxdescriptor leaks; applied inapps/desktop,packages/host-service, andpackages/pty-daemon.
- Pin
Summary by CodeRabbit
- New Features
- Added an idempotent
dispose()API for PTY instances (optionally preserving exit polling).
- Added an idempotent
- Bug Fixes
- Improved PTY/session cleanup on open/adopt failures, close, shutdown, and natural exits.
- Ensured stale exit events won’t affect newer sessions using the same id.
- Prevented fd/PTY leaks during partial adoption and exit callback ordering.
- Tests
- Expanded coverage for native/adopted master fd lifecycle, churn, forced termination, and handoff behavior.
- Strengthened integration and handler tests with dispose-aware fakes.
- Documentation
- Updated PTY daemon README with clearer fd lifecycle and testing guidance.