Repository navigation
fix(process-sessions): crash on stdin EPIPE, dropped login-shell args, Windows pty signal #401
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -322,8 +322,16 @@ export class ProcessSessionManager { | |
|
|
||
| private startPipe(session: ProcessSession, input: StartCommandInput): void { | ||
| const shell = resolveShellCommand(input.command); | ||
| const detached = process.platform !== "win32"; | ||
| const child = spawn(input.command, { | ||
| const windows = process.platform === "win32"; | ||
| const detached = !windows; | ||
| // Spawn the resolved shell directly (like the PTY path) so its full argument | ||
| // list survives: Node's `shell` option always invokes `<shell> -c <command>`, | ||
| // dropping the login-shell flag resolved for POSIX. On Windows, replicate | ||
| // what Node's `shell` option does for cmd.exe — verbatim arguments with the | ||
| // command wrapped in an extra pair of quotes — because cmd does not honor | ||
| // Node's default backslash escaping of embedded quotes. | ||
| const args = windows ? ["/d", "/s", "/c", `"${input.command}"`] : shell.args; | ||
| const child = spawn(shell.executable, args, { | ||
| cwd: input.cwd, | ||
| env: processEnvironment({ | ||
| workspaceId: input.workspaceId, | ||
|
|
@@ -332,14 +340,18 @@ export class ProcessSessionManager { | |
| stdio: "pipe", | ||
| windowsHide: true, | ||
| detached, | ||
| shell: shell.executable, | ||
| windowsVerbatimArguments: windows, | ||
| }); | ||
|
|
||
| session.process = { | ||
| write: (data) => child.stdin.write(data), | ||
| kill: (signal = "SIGTERM") => terminateProcessTree(child, signal, detached), | ||
| resize: input.tty ? () => undefined : undefined, | ||
| }; | ||
| // A stdin write racing child exit surfaces as an EPIPE 'error' event with no | ||
| // listener, which would crash the process; the 'close' handler already | ||
| // reports the session as ended, so the error can be ignored. | ||
| child.stdin.on("error", () => undefined); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Report stdin write failures while the session remains running. If a child closes stdin but keeps running, As per coding guidelines, “make tasks, inputs, outputs, failures, and ownership inspectable.” 🤖 Prompt for AI AgentsSource: Coding guidelines |
||
| child.stdout.on("data", (data: Buffer) => this.append(session, data.toString("utf8"))); | ||
| child.stderr.on("data", (data: Buffer) => this.append(session, data.toString("utf8"))); | ||
| child.on("error", (error) => this.append(session, `${error.message}\n`)); | ||
|
|
@@ -378,7 +390,14 @@ export class ProcessSessionManager { | |
| }; | ||
| pty.onData((data) => this.append(session, data)); | ||
| pty.onExit(({ exitCode, signal }) => { | ||
| this.finish(session, exitCode, signal === 0 ? undefined : String(signal)); | ||
| // node-pty only delivers a numeric signal on POSIX; on Windows it is | ||
| // undefined, and `String(undefined)` would record "undefined" for clean | ||
| // exits. | ||
| this.finish( | ||
| session, | ||
| exitCode, | ||
| typeof signal === "number" && signal !== 0 ? String(signal) : undefined, | ||
| ); | ||
| }); | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This Windows assertion never reaches the changed
onExithandler:start()usesstartPipeon Windows, even withtty: true. The POSIX assertion also passes with the old handler because a clean exit reports numeric zero. The existing process-session tests passed with the old signal expression restored, while a focused callback check distinguished the implementations forsignal: undefined.This is a non-blocking regression-coverage gap: both assertions can pass if the signal fix is removed. Add a focused test that supplies an absent signal to the PTY exit callback and fails with the old implementation.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Artifacts
Reproduction runner
Focused signal and dispatch probe
Relevant source lines and runtime environment
Executed source with old signal handler
Existing tests targeting old signal handler
Existing tests with old signal handler
Focused probe with old signal handler
Executed source with current signal handler
Existing tests targeting current signal handler
Existing tests with current signal handler
Focused probe with current signal handler
Tracked-file preservation and isolated mutation
Complete validation execution