mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-28 05:16:23 -06:00
fix(codex): preserve startup diagnostics on initialize timeout (#115161)
* fix(codex): preserve app-server startup diagnostics * chore: leave release notes to release automation
This commit is contained in:
committed by
GitHub
parent
bb634261cb
commit
c0df3cf0b9
@@ -597,6 +597,7 @@ describe("startCodexAttemptThread", () => {
|
||||
'import readline from "node:readline";',
|
||||
"const [requestLogPath, pidPath] = process.argv.slice(2);",
|
||||
'fs.writeFileSync(pidPath, String(process.pid), "utf8");',
|
||||
'process.stderr.write("Error: failed to initialize sqlite state runtime token=secret-value\\n");',
|
||||
"const lines = readline.createInterface({ input: process.stdin });",
|
||||
'lines.on("line", (line) => {',
|
||||
" const message = JSON.parse(line);",
|
||||
@@ -623,7 +624,9 @@ describe("startCodexAttemptThread", () => {
|
||||
skipStartSpy: true,
|
||||
});
|
||||
|
||||
await expect(run).rejects.toThrow("codex app-server initialize timed out");
|
||||
await expect(run).rejects.toThrow(
|
||||
'codex app-server initialize timed out; stderr="Error: failed to initialize sqlite state runtime token=<redacted>"',
|
||||
);
|
||||
|
||||
const requestMethods = (await fs.readFile(requestLogPath, "utf8")).trim().split(/\r?\n/u);
|
||||
expect(requestMethods).toEqual(["initialize"]);
|
||||
|
||||
@@ -355,6 +355,11 @@ export class CodexAppServerClient {
|
||||
return this.runtimeIdentity ? { ...this.runtimeIdentity } : undefined;
|
||||
}
|
||||
|
||||
/** Returns a bounded, redacted stderr diagnostic from the app-server process. */
|
||||
getStderrDiagnostic(): string | undefined {
|
||||
return redactCodexAppServerLinePreview(this.stderrTail) || undefined;
|
||||
}
|
||||
|
||||
/** Stable generation id for this exact physical client instance. */
|
||||
getInstanceId(): string {
|
||||
return this.instanceId;
|
||||
|
||||
@@ -715,6 +715,22 @@ describe("shared Codex app-server client", () => {
|
||||
expect(startSpy).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it("includes redacted app-server stderr when shared initialize times out", async () => {
|
||||
const harness = createClientHarness();
|
||||
vi.spyOn(CodexAppServerClient, "start").mockReturnValue(harness.client);
|
||||
|
||||
const models = listCodexAppServerModels({ timeoutMs: 100 });
|
||||
await vi.waitFor(() => expect(harness.writes.length).toBeGreaterThanOrEqual(1));
|
||||
harness.process.stderr.write(
|
||||
'Error: failed to initialize sqlite state runtime token="secret-value"\n',
|
||||
);
|
||||
|
||||
await expect(models).rejects.toThrow(
|
||||
'codex app-server initialize timed out; stderr="Error: failed to initialize sqlite state runtime token=\\"<redacted>\\""',
|
||||
);
|
||||
expect(harness.process.stdin.destroyed).toBe(true);
|
||||
});
|
||||
|
||||
it("keeps shared startup alive for a caller with a longer initialize timeout", async () => {
|
||||
const harness = createClientHarness();
|
||||
const startSpy = vi.spyOn(CodexAppServerClient, "start").mockReturnValue(harness.client);
|
||||
@@ -798,6 +814,20 @@ describe("shared Codex app-server client", () => {
|
||||
expect(harness.process.stdin.destroyed).toBe(true);
|
||||
});
|
||||
|
||||
it("includes redacted app-server stderr when isolated initialize times out", async () => {
|
||||
const harness = createClientHarness();
|
||||
vi.spyOn(CodexAppServerClient, "start").mockReturnValue(harness.client);
|
||||
|
||||
const client = createIsolatedCodexAppServerClient({ timeoutMs: 100 });
|
||||
await vi.waitFor(() => expect(harness.writes.length).toBeGreaterThanOrEqual(1));
|
||||
harness.process.stderr.write("state database is locked access_token=secret-value\n");
|
||||
|
||||
await expect(client).rejects.toThrow(
|
||||
'codex app-server initialize timed out; stderr="state database is locked access_token=<redacted>"',
|
||||
);
|
||||
expect(harness.process.stdin.destroyed).toBe(true);
|
||||
});
|
||||
|
||||
it("includes isolated auth application in the total startup deadline", async () => {
|
||||
const harness = createClientHarness();
|
||||
vi.spyOn(CodexAppServerClient, "start").mockReturnValue(harness.client);
|
||||
|
||||
@@ -39,6 +39,8 @@ import { withTimeout } from "./timeout.js";
|
||||
|
||||
export type { CodexAppServerPreparedAuth } from "./auth-bridge.js";
|
||||
|
||||
const CODEX_APP_SERVER_INITIALIZE_TIMEOUT_MESSAGE = "codex app-server initialize timed out";
|
||||
|
||||
type SharedCodexAppServerClientEntry = {
|
||||
client?: CodexAppServerClient;
|
||||
startup?: SharedCodexAppServerClientStartup;
|
||||
@@ -619,6 +621,8 @@ async function acquireSharedCodexAppServerClient(
|
||||
remainingTimeoutMs,
|
||||
startup.initialized,
|
||||
options?.abandonSignal,
|
||||
CODEX_APP_SERVER_INITIALIZE_TIMEOUT_MESSAGE,
|
||||
() => buildCodexAppServerInitializeTimeoutError(entry.client),
|
||||
);
|
||||
const client = await withCodexAppServerAcquireDeadline(
|
||||
timeoutMs,
|
||||
@@ -657,7 +661,8 @@ async function withCodexAppServerAcquireDeadline<T>(
|
||||
timeoutMs: number, // First: fail before the caller starts its promise argument.
|
||||
promise: Promise<T>,
|
||||
signal?: AbortSignal,
|
||||
timeoutMessage = "codex app-server initialize timed out",
|
||||
timeoutMessage = CODEX_APP_SERVER_INITIALIZE_TIMEOUT_MESSAGE,
|
||||
timeoutErrorFactory?: () => CodexAppServerStartupError,
|
||||
): Promise<T> {
|
||||
if (signal?.aborted) {
|
||||
throw new CodexAppServerStartupError("aborted", "codex app-server initialize aborted");
|
||||
@@ -666,7 +671,7 @@ async function withCodexAppServerAcquireDeadline<T>(
|
||||
promise,
|
||||
timeoutMs,
|
||||
timeoutMessage,
|
||||
() => new CodexAppServerStartupError("timed_out", timeoutMessage),
|
||||
() => timeoutErrorFactory?.() ?? new CodexAppServerStartupError("timed_out", timeoutMessage),
|
||||
);
|
||||
if (!signal) {
|
||||
return await timed;
|
||||
@@ -679,6 +684,18 @@ async function withCodexAppServerAcquireDeadline<T>(
|
||||
});
|
||||
}
|
||||
|
||||
function buildCodexAppServerInitializeTimeoutError(
|
||||
client: CodexAppServerClient | undefined,
|
||||
): CodexAppServerStartupError {
|
||||
const stderr = client?.getStderrDiagnostic();
|
||||
return new CodexAppServerStartupError(
|
||||
"timed_out",
|
||||
stderr
|
||||
? `${CODEX_APP_SERVER_INITIALIZE_TIMEOUT_MESSAGE}; stderr=${JSON.stringify(stderr)}`
|
||||
: CODEX_APP_SERVER_INITIALIZE_TIMEOUT_MESSAGE,
|
||||
);
|
||||
}
|
||||
|
||||
function resolveRemainingAcquireTimeout(timeoutMs: number, startedAt: number): number {
|
||||
if (!(timeoutMs > 0)) {
|
||||
return timeoutMs;
|
||||
@@ -847,6 +864,8 @@ async function startInitializedCodexAppServerClient(params: {
|
||||
resolveRemainingAcquireTimeout(timeoutMs, acquireStartedAt),
|
||||
(initialize = client.initialize()),
|
||||
params.abandonSignal,
|
||||
CODEX_APP_SERVER_INITIALIZE_TIMEOUT_MESSAGE,
|
||||
() => buildCodexAppServerInitializeTimeoutError(client),
|
||||
);
|
||||
} catch (error) {
|
||||
client.close();
|
||||
|
||||
Reference in New Issue
Block a user