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
@@ -319,12 +319,12 @@ describe('terminal runtime', () => {
expect(response.body).toEqual({ sessionId: 'term-1', cols: 120, rows: 40, status: 'running', mode: 'interactive', purpose: { type: 'terminal' } });
expect(harness.processes[0].options.cwd).toBe('/repo');
expect(harness.processes[0].options.env.COLORFGBG).toBe('0;15');
expect(harness.processes[0].options.env.NODE_CHANNEL_FD).toBe('');
expect(harness.processes[0].options.env).not.toHaveProperty('NODE_CHANNEL_FD');
expect(harness.processes[0].options.env).not.toHaveProperty('ARGV0');
expect(harness.processes[0].options.env).not.toHaveProperty('ELECTRON_RUN_AS_NODE');
if (process.platform === 'linux') {
if (process.platform !== 'win32') {
expect(harness.processes[0].shell).toMatch(/\/env$/);
expect(harness.processes[0].args.slice(0, 3)).toEqual(['-u', 'ARGV0', expect.any(String)]);
expect(harness.processes[0].args.slice(0, 5)).toEqual(['-u', 'ARGV0', '-u', 'NODE_CHANNEL_FD', expect.any(String)]);
}
harness.processes[0].emitData('\u001b[?2031h\u001b]10;?\u0007\u001b]11;?\u0007\u001b[0c');
expect(harness.processes[0].writes).toEqual(['\u001b]10;rgb:1b1b/1b1b/1b1b\u001b\\', '\u001b]11;rgb:fafa/f8f8/f0f0\u001b\\', '\u001b[?1;2c']);
@@ -380,7 +380,7 @@ describe('terminal runtime', () => {
await harness.routes.post.get('/api/terminal/create')({ body: { sessionId: 'term-argv0', cwd: '/repo', cols: 80, rows: 24 } }, response);
expect(response.statusCode).toBe(200);
expect(harness.processes[0].options.env).not.toHaveProperty('ARGV0');
if (process.platform === 'linux') {
if (process.platform !== 'win32') {
expect(harness.processes[0].shell).toMatch(/\/env$/);
expect(harness.processes[0].args[0]).toBe('-u');
expect(harness.processes[0].args[1]).toBe('ARGV0');
@@ -417,9 +417,9 @@ describe('terminal runtime', () => {
const created = createResponse();
await harness.routes.post.get('/api/terminal/create')({ body: { sessionId: 'term-shell', cwd: '/repo', shell: 'zsh', loginShell: true } }, created);
expect(created.statusCode).toBe(200);
if (process.platform === 'linux') {
if (process.platform !== 'win32') {
expect(harness.processes[0].shell).toMatch(/\/env$/);
expect(harness.processes[0].args).toEqual(['-u', 'ARGV0', '/bin/zsh', '-l']);
expect(harness.processes[0].args).toEqual(['-u', 'ARGV0', '-u', 'NODE_CHANNEL_FD', '/bin/zsh', '-l']);
} else {
expect(harness.processes[0].shell).toBe('/bin/zsh');
expect(harness.processes[0].args).toEqual(['-l']);
@@ -428,9 +428,9 @@ describe('terminal runtime', () => {
const restarted = createResponse();
await harness.routes.post.get('/api/terminal/:sessionId/restart')({ params: { sessionId: 'term-shell' }, body: { shell: 'bash', loginShell: true } }, restarted);
expect(restarted.statusCode).toBe(200);
if (process.platform === 'linux') {
if (process.platform !== 'win32') {
expect(harness.processes[1].shell).toMatch(/\/env$/);
expect(harness.processes[1].args).toEqual(['-u', 'ARGV0', '/bin/bash', '-l']);
expect(harness.processes[1].args).toEqual(['-u', 'ARGV0', '-u', 'NODE_CHANNEL_FD', '/bin/bash', '-l']);
} else {
expect(harness.processes[1].shell).toBe('/bin/bash');
expect(harness.processes[1].args).toEqual(['-l']);
@@ -757,9 +757,9 @@ describe('terminal runtime', () => {
expect(response.statusCode).toBe(200);
expect(response.body).toEqual({ sessionId: 'term-command', cols: 80, rows: 24, status: 'running', mode: 'command', purpose: { type: 'terminal' } });
if (process.platform === 'linux') {
if (process.platform !== 'win32') {
expect(harness.processes[0].shell).toMatch(/\/env$/);
expect(harness.processes[0].args).toEqual(['-u', 'ARGV0', '/bin/bash', '-l', '-i', '-c', 'printf ready']);
expect(harness.processes[0].args).toEqual(['-u', 'ARGV0', '-u', 'NODE_CHANNEL_FD', '/bin/bash', '-l', '-i', '-c', 'printf ready']);
} else {
expect(harness.processes[0].args).toEqual(['-l', '-i', '-c', 'printf ready']);
}