orca/src/main/daemon
Jinwoo Hong a638215729
fix(pty): release ptmx fd on natural exit + defuse SIGHUP-to-recycled-pid (#1327)
* fix(pty): release ptmx fd on natural exit + defuse SIGHUP-to-recycled-pid

Daemons accumulated ptmx fds over time because node-pty's UnixTerminal
only releases the master fd when destroy() runs. On the natural-exit
path (the common case — user closes a tab, shell runs `exit`) nothing
ever calls destroy(), so the fd leaks until GC. On macOS this
eventually hits kern.tty.ptmx_max=511 and all new terminals fail to
spawn.

Fix: release the fd synchronously on every teardown path (natural
exit, explicit kill, stale SSH spawn, daemon shutdown) and close the
concurrent SIGHUP-to-recycled-pid hazard inside node-pty's
UnixTerminal.destroy().

- src/main/daemon/pty-subprocess.ts: synchronous POSIX proc.kill
  neutralization inside proc.onExit; dead guards on forceKill/signal
  so they never target a reaped-and-possibly-recycled pid
- src/main/daemon/session.ts: new disposeSubprocess() for already-
  exited sessions (fd release only, no SIGKILL) — avoids sending
  SIGKILL to a recycled pid during daemon shutdown
- src/main/daemon/terminal-host.ts: dispose loop routes on isAlive —
  live sessions get forceKillAndDisposeSubprocess (SIGKILL + fd
  release), exited sessions get disposeSubprocess (fd release only)
- src/main/providers/local-pty-provider.ts: same POSIX kill
  neutralization at top of onExit for the legacy local path
- src/relay/pty-handler.ts: same neutralization in wireAndStore;
  disposed flag guards all public entry points; dispose() uses
  SIGKILL (not SIGTERM) before destroy since the relay is exiting;
  killTimer fallback + immediate-shutdown + stale-spawn cleanup all
  call disposeManagedPty + ptys.delete so wedged children (D-state,
  bad NFS) can't leak map entries against the 50-PTY cap

Windows is exempt everywhere — WindowsTerminal.destroy IS a kill()
call internally (closes the ConPTY agent), so neutralizing would
turn destroy into a no-op and leak the agent.

See docs/fix-pty-fd-leak.md for the full design.

Co-authored-by: Orca <help@stably.ai>

* fix(pty): patch node-pty native off-by-one leaking /dev/ptmx per spawn

node-pty 1.1.0's pty_posix_spawn on macOS walks low_fds[0..2] in an
allocation loop that breaks at the first fd >= STDERR_FILENO, then
cleans up via `for (; count > 0; count--) close(low_fds[count])`. In
the typical case (break at count=0) the cleanup body never runs and
low_fds[0] — a /dev/ptmx handle — leaks per spawn. Fixed upstream in
microsoft/node-pty af053f2 (PR #882), not in any 1.1.0 release.

Backport the 3-line cleanup-loop fix as a pnpm patch. E2E validated
against a dev daemon: 200 spawn/kill cycles kept the daemon's ptmx
fd count flat at baseline; prior runs reproduced linear 1-per-spawn
growth. Also documents the native root cause as a status addendum in
docs/fix-pty-fd-leak.md — the JS-side destroy() discipline previously
landed is still load-bearing for the SIGHUP-to-recycled-pid hazard and
for synchronous fd release on daemon shutdown.

Co-authored-by: Orca <help@stably.ai>

* fix(pty): capture stable kill spy ref in pty.test.ts

destroyPtyProcess reassigns proc.kill = () => {} on POSIX to defuse
the SIGHUP-to-recycled-pid hazard (see docs/fix-pty-fd-leak.md). After
that reassignment, proc.kill.mock is undefined and the assertions
crashed in CI. Capture a stable reference to the vi.fn() before it
gets reassigned.

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Orca <help@stably.ai>
2026-05-02 23:12:14 -07:00
..
fixtures feat: terminal persistence via out-of-process daemon (#729) 2026-04-17 01:42:41 -04:00
binary-frame.test.ts feat: terminal persistence via out-of-process daemon (#729) 2026-04-17 01:42:41 -04:00
binary-frame.ts feat: terminal persistence via out-of-process daemon (#729) 2026-04-17 01:42:41 -04:00
client.test.ts feat: terminal persistence via out-of-process daemon (#729) 2026-04-17 01:42:41 -04:00
client.ts Fix daemon shell-ready wrapper persistence (#1025) 2026-04-24 13:01:27 -07:00
daemon-entry.test.ts feat: terminal persistence via out-of-process daemon (#729) 2026-04-17 01:42:41 -04:00
daemon-entry.ts fix(terminal): guard PTY native calls against dead-process Napi::Error crash (#916) 2026-04-22 11:16:43 -07:00
daemon-health.test.ts fix(pty): release ptmx fd on natural exit + defuse SIGHUP-to-recycled-pid (#1327) 2026-05-02 23:12:14 -07:00
daemon-health.ts feat(settings): manage sessions panel + pid-recycling daemon guard (#1343) 2026-05-02 23:05:44 -07:00
daemon-init.test.ts feat(settings): manage sessions panel + pid-recycling daemon guard (#1343) 2026-05-02 23:05:44 -07:00
daemon-init.ts feat(settings): manage sessions panel + pid-recycling daemon guard (#1343) 2026-05-02 23:05:44 -07:00
daemon-main.test.ts fix(pty): release ptmx fd on natural exit + defuse SIGHUP-to-recycled-pid (#1327) 2026-05-02 23:12:14 -07:00
daemon-main.ts feat: terminal persistence via out-of-process daemon (#729) 2026-04-17 01:42:41 -04:00
daemon-pty-adapter.test.ts fix(pty): release ptmx fd on natural exit + defuse SIGHUP-to-recycled-pid (#1327) 2026-05-02 23:12:14 -07:00
daemon-pty-adapter.ts feat(settings): manage sessions panel + pid-recycling daemon guard (#1343) 2026-05-02 23:05:44 -07:00
daemon-pty-provider.test.ts fix(pty): release ptmx fd on natural exit + defuse SIGHUP-to-recycled-pid (#1327) 2026-05-02 23:12:14 -07:00
daemon-pty-provider.ts feat: terminal persistence via out-of-process daemon (#729) 2026-04-17 01:42:41 -04:00
daemon-pty-router.test.ts feat(settings): manage sessions panel + pid-recycling daemon guard (#1343) 2026-05-02 23:05:44 -07:00
daemon-pty-router.ts feat(settings): manage sessions panel + pid-recycling daemon guard (#1343) 2026-05-02 23:05:44 -07:00
daemon-pty-size.ts Fix daemon shell-ready wrapper persistence (#1025) 2026-04-24 13:01:27 -07:00
daemon-server.test.ts fix(pty): release ptmx fd on natural exit + defuse SIGHUP-to-recycled-pid (#1327) 2026-05-02 23:12:14 -07:00
daemon-server.ts feat: add PowerShell 7+ support for Windows terminals 2026-05-02 16:06:11 -07:00
daemon-spawner.test.ts fix(pty): release ptmx fd on natural exit + defuse SIGHUP-to-recycled-pid (#1327) 2026-05-02 23:12:14 -07:00
daemon-spawner.ts feat(settings): manage sessions panel + pid-recycling daemon guard (#1343) 2026-05-02 23:05:44 -07:00
headless-emulator.test.ts fix(term): stop daemon emulator from replying to xterm queries (#1024) 2026-04-24 14:05:42 -07:00
headless-emulator.ts fix(term): stop daemon emulator from replying to xterm queries (#1024) 2026-04-24 14:05:42 -07:00
history-manager.test.ts fix(daemon): checkpoint-based history persistence to reduce memory (#1139) 2026-04-26 23:44:08 -07:00
history-manager.ts fix(daemon): checkpoint-based history persistence to reduce memory (#1139) 2026-04-26 23:44:08 -07:00
history-paths.ts feat: terminal persistence via out-of-process daemon (#729) 2026-04-17 01:42:41 -04:00
history-reader.test.ts fix(daemon): checkpoint-based history persistence to reduce memory (#1139) 2026-04-26 23:44:08 -07:00
history-reader.ts fix(daemon): checkpoint-based history persistence to reduce memory (#1139) 2026-04-26 23:44:08 -07:00
ndjson.test.ts feat: terminal persistence via out-of-process daemon (#729) 2026-04-17 01:42:41 -04:00
ndjson.ts feat: terminal persistence via out-of-process daemon (#729) 2026-04-17 01:42:41 -04:00
post-ready-flush-gate.test.ts fix(daemon): defer startup flush past shell raw-mode switch (#1060) 2026-04-24 15:26:50 -07:00
post-ready-flush-gate.ts fix(daemon): defer startup flush past shell raw-mode switch (#1060) 2026-04-24 15:26:50 -07:00
priority-semaphore.test.ts feat: terminal persistence via out-of-process daemon (#729) 2026-04-17 01:42:41 -04:00
priority-semaphore.ts feat: terminal persistence via out-of-process daemon (#729) 2026-04-17 01:42:41 -04:00
production-launcher.test.ts fix(pty): release ptmx fd on natural exit + defuse SIGHUP-to-recycled-pid (#1327) 2026-05-02 23:12:14 -07:00
production-launcher.ts feat: terminal persistence via out-of-process daemon (#729) 2026-04-17 01:42:41 -04:00
pty-session-id.test.ts refactor(pty): extract sessionId helper and harden daemon spawn cleanup (#1216) 2026-04-28 10:08:00 -07:00
pty-session-id.ts refactor(pty): extract sessionId helper and harden daemon spawn cleanup (#1216) 2026-04-28 10:08:00 -07:00
pty-subprocess.test.ts fix(pty): release ptmx fd on natural exit + defuse SIGHUP-to-recycled-pid (#1327) 2026-05-02 23:12:14 -07:00
pty-subprocess.ts fix(pty): release ptmx fd on natural exit + defuse SIGHUP-to-recycled-pid (#1327) 2026-05-02 23:12:14 -07:00
reattach-snapshot.test.ts fix(pty): release ptmx fd on natural exit + defuse SIGHUP-to-recycled-pid (#1327) 2026-05-02 23:12:14 -07:00
session.test.ts fix(pty): release ptmx fd on natural exit + defuse SIGHUP-to-recycled-pid (#1327) 2026-05-02 23:12:14 -07:00
session.ts fix(pty): release ptmx fd on natural exit + defuse SIGHUP-to-recycled-pid (#1327) 2026-05-02 23:12:14 -07:00
shell-ready.test.ts Fix shell-ready zsh wrapper self loops (#1184) 2026-04-27 12:13:45 -07:00
shell-ready.ts Fix shell-ready zsh wrapper self loops (#1184) 2026-04-27 12:13:45 -07:00
terminal-host-startup.test.ts fix(pty): release ptmx fd on natural exit + defuse SIGHUP-to-recycled-pid (#1327) 2026-05-02 23:12:14 -07:00
terminal-host.test.ts fix(pty): release ptmx fd on natural exit + defuse SIGHUP-to-recycled-pid (#1327) 2026-05-02 23:12:14 -07:00
terminal-host.ts fix(pty): release ptmx fd on natural exit + defuse SIGHUP-to-recycled-pid (#1327) 2026-05-02 23:12:14 -07:00
types.ts feat(settings): manage sessions panel + pid-recycling daemon guard (#1343) 2026-05-02 23:05:44 -07:00
xterm-env-polyfill.ts feat: terminal persistence via out-of-process daemon (#729) 2026-04-17 01:42:41 -04:00