fix(terminal): unset NODE_CHANNEL_FD for PTY shells on every POSIX host

The PTY runtime exported an empty NODE_CHANNEL_FD to override the daemon's
IPC descriptor, because bun-pty merges the native environ back into the
child and a JS-only delete does not stick. Node CLIs launched from the
shell (opencode, claude) then printed "warn: Failed to parse IPC channel
number ''" on exit.

The Linux-only env -u ARGV0 wrapper now applies on macOS and Linux and
also unsets NODE_CHANNEL_FD, so the variable is gone instead of empty.

Testing: runtime and inherited-env tests updated for the POSIX wrapper;
verified in the running app that exiting opencode no longer prints the
warning.
This commit is contained in:
Bohdan Triapitsyn
2026-09-07 12:18:22 +03:00
parent 39fa8c1917
commit ff75dc9bd5
5 changed files with 43 additions and 36 deletions
+16 -9
View File
@@ -12,7 +12,9 @@
import { createRequire } from 'node:module';
import { existsSync } from 'node:fs';
const LINUX_ENV_BINARIES = ['/usr/bin/env', '/bin/env'];
const POSIX_ENV_BINARIES = ['/usr/bin/env', '/bin/env'];
/** Variables a PTY shell must never inherit from the OpenChamber host process. */
const PTY_HOST_PRIVATE_VARIABLES = Object.freeze(['ARGV0', 'NODE_CHANNEL_FD']);
/**
* Remove AppImage `ARGV0` from a mutable env object (or `process.env`).
@@ -49,26 +51,31 @@ export function clearAppImageArgv0FromProcessEnv() {
}
/**
* Resolve a Linux PTY launch that drops native `ARGV0` before the shell starts.
* Resolve a POSIX PTY launch that unsets host-private variables before the
* shell starts.
*
* `bun-pty` merges the OS environ into the child, so deleting `ARGV0` from the
* JS env object alone is not enough. Wrapping with `env -u ARGV0` unsets it
* before execing the real shell. No-op on non-Linux platforms.
* `bun-pty` merges the OS environ into the child, so deleting a variable from
* the JS env object alone is not enough: AppImage `ARGV0` came back on Linux,
* and the daemon's `NODE_CHANNEL_FD` came back everywhere (Node then warns
* "Failed to parse IPC channel number" when a CLI such as opencode exits).
* Wrapping with `env -u NAME ...` unsets them before execing the real shell.
* No-op on Windows and when no `env` binary is found.
*
* @param {string} executable
* @param {string[]} args
* @param {readonly string[]} variables
* @returns {{ executable: string, args: string[] }}
*/
export function resolveLinuxPtyLaunch(executable, args = []) {
if (process.platform !== 'linux') {
export function resolvePosixPtyLaunch(executable, args = [], variables = PTY_HOST_PRIVATE_VARIABLES) {
if (process.platform === 'win32' || variables.length === 0) {
return { executable, args };
}
const envBinary = LINUX_ENV_BINARIES.find((candidate) => existsSync(candidate));
const envBinary = POSIX_ENV_BINARIES.find((candidate) => existsSync(candidate));
if (!envBinary) {
return { executable, args };
}
return {
executable: envBinary,
args: ['-u', 'ARGV0', executable, ...args],
args: [...variables.flatMap((name) => ['-u', name]), executable, ...args],
};
}