fix(next): ProcessBox never reads the pid of a process that has not launched; a child that never reads stdin no longer crashes PhoneProcess

Merged
TE
7d ago
+112-7

Two process-lifecycle crashes in the app.

Problems

  • ProcessBox.kill() (ProcessRunner) read process.processIdentifier before checking isRunning. processIdentifier of a Process that has not launched raises, and run has no cancellation check: a caller that is already cancelled gets onCancel (which calls kill()) before the child task has launched anything. Callers include the daemon launcher, login-environment capture, cloud credential login and the server reach/ensure paths. Every other kill site in the app (FixChild, CloudCodexBrokerSession.forceStop, HostCLI.stop) already checks isRunning first.
  • PhoneProcess wrote the child's stdin with FileHandle.write(_:). A child that exits without reading (a simulator that is not booted makes simctl pbcopy do this) closes the read end, the write fails with EPIPE, and write(_:) raises it as an uncatchable Objective-C exception (SIGPIPE ends a process that does not ignore it). A paste larger than a pipe buffer makes the window certain.

Fix

  • kill() checks isRunning before it reads the pid; run starts with Task.checkCancellation(), so a cancelled caller launches no child.
  • PhoneProcess sets F_SETNOSIGPIPE on the stdin pipe and writes with write(contentsOf:) (the failure is ignored: the child's exit status says what went wrong).

Tests

First commit is the tests alone, second the fixes.

  • ProcessRunnerCancellationTests: kill on a Process that never launched sends nothing; an already-cancelled ProcessRunner.run throws CancellationError and never starts the child.
  • PhoneProcessBrokenStdinTests: a 4 MB stdin to true ends the run with status 0 instead of crashing.

No capture: no visible change; process launch/cancel and stdin handling.

Agent session: agent session