Skip to content

Commit c99a46c

Browse files
committed
fix(codex): clean up early bootstrap returns
1 parent 3e0d151 commit c99a46c

2 files changed

Lines changed: 41 additions & 9 deletions

File tree

extensions/codex/src/app-server/run-attempt.context-engine.test.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { SessionManager } from "@earendil-works/pi-coding-agent";
66
import type { EmbeddedRunAttemptParams } from "openclaw/plugin-sdk/agent-harness";
77
import {
88
embeddedAgentLog,
9+
nativeHookRelayTesting,
910
PREEMPTIVE_OVERFLOW_ERROR_TEXT,
1011
queueAgentHarnessMessage,
1112
type HarnessContextEngine as ContextEngine,
@@ -311,6 +312,22 @@ function getRequestInputTextAt(
311312
.join("\n");
312313
}
313314

315+
function extractRelayIdFromThreadRequest(params: unknown): string {
316+
const config = (params as { config?: Record<string, unknown> }).config;
317+
const entries = config?.["hooks.PreToolUse"];
318+
if (!Array.isArray(entries)) {
319+
throw new Error("pre-tool hook config missing");
320+
}
321+
const command = (entries as Array<{ hooks?: Array<{ command?: string }> }>)
322+
.flatMap((entry) => entry.hooks ?? [])
323+
.find((hook) => typeof hook.command === "string")?.command;
324+
const match = command?.match(/--relay-id ([^ ]+)/);
325+
if (!match?.[1]) {
326+
throw new Error(`relay id missing from command: ${command}`);
327+
}
328+
return match[1];
329+
}
330+
314331
type CodexNativeThreadLifecycleEvent = Extract<
315332
DiagnosticEventPayload,
316333
{ type: "codex.native_thread.lifecycle" }
@@ -337,6 +354,7 @@ describe("runCodexAppServerAttempt context-engine lifecycle", () => {
337354

338355
afterEach(async () => {
339356
resetCodexAppServerClientFactoryForTest();
357+
nativeHookRelayTesting.clearNativeHookRelaysForTests();
340358
resetDiagnosticEventsForTest();
341359
vi.restoreAllMocks();
342360
await fs.rm(tempDir, { recursive: true, force: true });
@@ -1568,14 +1586,19 @@ describe("runCodexAppServerAttempt context-engine lifecycle", () => {
15681586
params.contextEngine = contextEngine;
15691587
params.contextTokenBudget = 16_000;
15701588

1571-
const result = await runCodexAppServerAttempt(params);
1589+
const result = await runCodexAppServerAttempt(params, {
1590+
nativeHookRelay: { enabled: true, events: ["pre_tool_use"] },
1591+
});
15721592

15731593
expect(result.promptError).toBe(PREEMPTIVE_OVERFLOW_ERROR_TEXT);
15741594
expect(result.promptErrorSource).toBe("precheck");
15751595
expect(result.preflightRecovery?.route).not.toBe("fits");
15761596
expect(compact).not.toHaveBeenCalled();
15771597
expect(assemble).toHaveBeenCalledTimes(2);
15781598
expect(await readCodexAppServerBinding(sessionFile)).toBeUndefined();
1599+
const startRequest = harness.requests.find((request) => request.method === "thread/start");
1600+
const relayId = extractRelayIdFromThreadRequest(startRequest?.params);
1601+
expect(nativeHookRelayTesting.getNativeHookRelayRegistrationForTests(relayId)).toBeUndefined();
15791602
expect(harness.requests.map((request) => request.method)).toEqual([
15801603
"thread/resume",
15811604
"thread/start",

extensions/codex/src/app-server/run-attempt.ts

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2036,6 +2036,15 @@ export async function runCodexAppServerAttempt(
20362036
await releaseCodexSandboxExecServerEnvironment(sandbox);
20372037
}
20382038
};
2039+
const cleanupStartedThreadBeforeTurnReturn = async () => {
2040+
nativeHookRelay?.unregister();
2041+
nativeHookRelay = undefined;
2042+
await releaseSandboxExecEnvironment();
2043+
params.abortSignal?.removeEventListener("abort", abortFromUpstream);
2044+
releaseSharedClientLease?.();
2045+
releaseSharedClientLease = undefined;
2046+
startupClientForCleanup = undefined;
2047+
};
20392048
let codexEnvironmentSelection: CodexTurnEnvironmentParams[] | undefined;
20402049
let codexExecutionCwd = effectiveWorkspace;
20412050
let codexSandboxPolicy: CodexSandboxPolicy | undefined;
@@ -2357,20 +2366,20 @@ export async function runCodexAppServerAttempt(
23572366
threadId: thread.threadId,
23582367
timeoutMs: CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS,
23592368
});
2360-
await releaseSandboxExecEnvironment();
2361-
params.abortSignal?.removeEventListener("abort", abortFromUpstream);
2362-
releaseSharedClientLease?.();
2363-
releaseSharedClientLease = undefined;
2369+
await cleanupStartedThreadBeforeTurnReturn();
23642370
return providerBoundaryPrecheckFailure;
23652371
}
23662372
} catch (error) {
2367-
params.abortSignal?.removeEventListener("abort", abortFromUpstream);
23682373
if (runAbortController.signal.aborted || isAbortLikeError(error)) {
2369-
await releaseSandboxExecEnvironment();
2370-
releaseSharedClientLease?.();
2371-
releaseSharedClientLease = undefined;
2374+
await clearCodexAppServerBinding(activeSessionFile);
2375+
await unsubscribeCodexThreadBestEffort(client, {
2376+
threadId: thread.threadId,
2377+
timeoutMs: CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS,
2378+
});
2379+
await cleanupStartedThreadBeforeTurnReturn();
23722380
return buildPreStartAbortResult(error);
23732381
}
2382+
params.abortSignal?.removeEventListener("abort", abortFromUpstream);
23742383
throw error;
23752384
}
23762385
systemPromptReport = buildCurrentSystemPromptReport();

0 commit comments

Comments
 (0)