Skip to content

Commit f7d1125

Browse files
fix: include stderr in process exceptions (#212)
1 parent e26fe30 commit f7d1125

3 files changed

Lines changed: 51 additions & 2 deletions

File tree

src/CodexAcpServer.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ export class CodexAcpServer implements acp.Agent {
113113
private readonly connection: acp.AgentSideConnection;
114114
private readonly defaultAuthRequest: CodexAuthRequest | null;
115115
private readonly getExitCode: () => number | null;
116+
private readonly getRecentStderr: () => string;
116117
private readonly availableCommands: CodexCommands;
117118
private clientInfo: acp.Implementation | null;
118119
private terminalOutputMode: TerminalOutputMode;
@@ -130,6 +131,7 @@ export class CodexAcpServer implements acp.Agent {
130131
codexAcpClient: CodexAcpClient,
131132
defaultAuthRequest?: CodexAuthRequest,
132133
getExitCode?: () => number | null,
134+
getRecentStderr?: () => string,
133135
) {
134136
this.sessions = new Map();
135137
this.pendingMcpStartupSessions = new Map();
@@ -142,6 +144,7 @@ export class CodexAcpServer implements acp.Agent {
142144
this.codexAcpClient = codexAcpClient;
143145
this.defaultAuthRequest = defaultAuthRequest ?? null;
144146
this.getExitCode = getExitCode ?? (() => null);
147+
this.getRecentStderr = getRecentStderr ?? (() => "");
145148
this.clientInfo = null;
146149
this.terminalOutputMode = "terminal_output_delta";
147150
this.availableCommands = new CodexCommands(
@@ -1458,7 +1461,9 @@ export class CodexAcpServer implements acp.Agent {
14581461
throw new RequestError(requestErrorCode, `VC++ redistributable should be installed`);
14591462
}
14601463
if (exitCode !== null) {
1461-
throw new RequestError(requestErrorCode, `Codex process has exited with code ${exitCode}`);
1464+
const stderr = this.getRecentStderr().trim();
1465+
const detail = stderr ? `:\n${stderr}` : "";
1466+
throw new RequestError(requestErrorCode, `Codex process has exited with code ${exitCode}${detail}`);
14621467
}
14631468
throw err;
14641469
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { describe, it, expect } from 'vitest';
2+
import * as fs from 'node:fs';
3+
import * as os from 'node:os';
4+
import * as path from 'node:path';
5+
import { once } from 'node:events';
6+
import * as acp from '@agentclientprotocol/sdk';
7+
import { startCodexConnection } from '../../CodexJsonRpcConnection';
8+
import { CodexAppServerClient } from '../../CodexAppServerClient';
9+
import { CodexAcpClient } from '../../CodexAcpClient';
10+
import { CodexAcpServer } from '../../CodexAcpServer';
11+
import { createMockConnections } from './test-utils';
12+
13+
describe('CodexACPAgent - process exit error', () => {
14+
it.skipIf(process.platform === 'win32')('includes the crashed process stderr', async () => {
15+
const fakeCodex = path.join(fs.mkdtempSync(path.join(os.tmpdir(), 'codex-bad-')), 'codex');
16+
fs.writeFileSync(fakeCodex, "#!/bin/sh\necho 'codex: failed to launch' >&2\nexit 1\n");
17+
fs.chmodSync(fakeCodex, 0o755);
18+
19+
const connection = startCodexConnection(fakeCodex);
20+
let stderr = '';
21+
connection.process.stderr.on('data', (chunk: Buffer) => { stderr += chunk.toString(); });
22+
23+
const codexClient = new CodexAcpClient(new CodexAppServerClient(connection.connection));
24+
const agent = new CodexAcpServer(
25+
createMockConnections().mockAcpConnection,
26+
codexClient,
27+
undefined,
28+
() => connection.process.exitCode,
29+
() => stderr,
30+
);
31+
32+
await once(connection.process, 'close'); // process exited and stderr flushed
33+
34+
await expect(agent.initialize({ protocolVersion: acp.PROTOCOL_VERSION }))
35+
.rejects.toThrow("Codex process has exited with code 1:\ncodex: failed to launch");
36+
});
37+
});

src/index.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,13 @@ function startAcpServer() {
5757
});
5858

5959
const codexConnection = startCodexConnection(codexPath);
60+
61+
const maxStderrTailChars = 2 * 1024;
62+
let stderr = "";
63+
codexConnection.process.stderr.addListener("data", (data: Buffer) => {
64+
stderr = (stderr + data.toString()).slice(-maxStderrTailChars);
65+
});
66+
6067
process.stdin.on("close", (chunk: Buffer) => {
6168
codexConnection.process.stdin.end();
6269
// Kill the codex process if it doesn't exit naturally
@@ -73,7 +80,7 @@ function startAcpServer() {
7380
function createAgent(connection: acp.AgentSideConnection): CodexAcpServer {
7481
const appServerClient = new CodexAppServerClient(codexConnection.connection);
7582
const codexClient = new CodexAcpClient(appServerClient, config, modelProvider);
76-
return new CodexAcpServer(connection, codexClient, defaultAuthRequest, () => codexConnection.process.exitCode);
83+
return new CodexAcpServer(connection, codexClient, defaultAuthRequest, () => codexConnection.process.exitCode, () => stderr);
7784
}
7885

7986
new acp.AgentSideConnection(createAgent, acpJsonStream);

0 commit comments

Comments
 (0)