test(runner): qualify live native Codex resume

Keep completed semantic receipts bounded after terminal turns and add an opt-in live Codex qualification path around the production Runner D restart test.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-08-26 19:42:12 +00:00
1 parent eb191dddc9
commit d9a356b0a4
2 files changed
+57 -18

No files matched your search

@@ -929,8 +929,16 @@ impl CodexCommandExecutor {
}
}
if method == "turn/completed" {
if !state.pending_semantic_calls.is_empty() {
return Err(DurableRunnerError::invalid(
"Codex completed a turn with pending semantic tool calls",
));
}
state.active_provider_turn_id = None;
state.lifecycle = "session_open".to_owned();
// A completed turn cannot reissue its tool requests. Keep results
// only through that boundary so long-lived sessions stay bounded.
state.completed_semantic_calls.clear();
}
state.extend_events(normalized)?;
if let Some(event_type) = terminal_event_type {
@@ -176,6 +176,8 @@ describeEmbeddedPostgres("native Codex server vertical slice", () => {
});
const logs: string[] = [];
const runnerPids: number[] = [];
const liveCodex = process.env.PAPERCLIP_LIVE_CODEX_NATIVE_RESUME === "1";
let semanticCallId: string | null = null;
let resolveRestart!: () => void;
const restarted = new Promise<void>((resolve) => {
resolveRestart = resolve;
@@ -192,30 +194,35 @@ describeEmbeddedPostgres("native Codex server vertical slice", () => {
turnId: native.turnId,
itemId: native.itemId,
cwd: tmpdir(),
prompt: "Complete the fake native Codex turn.",
model: "test-model",
prompt: liveCodex
? "Call report_progress exactly once with body 'Native resume completed one semantic effect.' and idempotencyKey 'native-resume-proof-1'. Then briefly state that the progress update is complete. Do not call any other tool."
: "Complete the fake native Codex turn.",
model: liveCodex ? "gpt-5.6-sol" : "test-model",
resumeProviderSessionId: null,
completionContract: native.completionContract,
timeoutMs: 30_000,
timeoutMs: liveCodex ? 180_000 : 30_000,
environment: {},
runnerBinary,
runtimeRoot,
providerLaunch: {
command: fakeCodexBinary,
args: [
"--state-file",
resolve(runtimeRoot, "fake-codex-state.json"),
"--call-log",
resolve(runtimeRoot, "fake-codex-calls.log"),
"--emit-semantic-tool",
],
providerVersion: "fake-codex-v1",
},
...(liveCodex ? {} : {
providerLaunch: {
command: fakeCodexBinary,
args: [
"--state-file",
resolve(runtimeRoot, "fake-codex-state.json"),
"--call-log",
resolve(runtimeRoot, "fake-codex-calls.log"),
"--emit-semantic-tool",
],
providerVersion: "fake-codex-v1",
},
}),
onLog: async (_stream, chunk) => {
logs.push(chunk);
},
onSemanticToolInputCommitted: async ({ callId, operationId }) => {
expect(callId).toBe("semantic-call-1");
semanticCallId = callId;
if (!liveCodex) expect(callId).toBe("semantic-call-1");
expect(operationId).toBe("report_progress");
const firstPid = runnerPids[0];
if (!firstPid) throw new Error("First Runner D process was not recorded");
@@ -238,8 +245,6 @@ describeEmbeddedPostgres("native Codex server vertical slice", () => {
signal: null,
timedOut: false,
provider: "codex",
sessionParams: { sessionId: "codex-thread-1" },
summary: "Codex completed the fake turn.",
resultJson: {
nativeRunner: {
result: {
@@ -257,6 +262,12 @@ describeEmbeddedPostgres("native Codex server vertical slice", () => {
},
},
});
if (!liveCodex) {
expect(result).toMatchObject({
sessionParams: { sessionId: "codex-thread-1" },
summary: "Codex completed the fake turn.",
});
}
expect(logs.join("\n")).not.toContain("PAPERCLIP_RUNNER_BOOTSTRAP_TICKET");
expect(runnerPids).toHaveLength(2);
expect(new Set(runnerPids).size).toBe(2);
@@ -297,7 +308,7 @@ describeEmbeddedPostgres("native Codex server vertical slice", () => {
| undefined;
expect(
(semanticPayload?.semantic_tool as Record<string, unknown>)?.callId,
).toBe("semantic-call-1");
).toBe(semanticCallId);
const progressEffects = await db
.select({ body: issueComments.body })
.from(issueComments)
@@ -311,6 +322,26 @@ describeEmbeddedPostgres("native Codex server vertical slice", () => {
{ body: "Native resume completed one semantic effect." },
]);
if (liveCodex) {
console.log("NATIVE_CODEX_RESUME_PROOF", JSON.stringify({
appCommit: process.env.PAPERCLIP_LIVE_CODEX_APP_COMMIT ?? null,
runId,
providerSessionId: result.sessionParams?.sessionId ?? null,
semanticCallId,
runnerPids,
semanticInputCount: semanticInputs.length,
semanticReconciledCount: nativeEvents.filter(
(event) => event.eventType === "semantic_tool.reconciled",
).length,
semanticResultCount: nativeEvents.filter(
(event) => event.eventType === "semantic_tool.result",
).length,
controlPlaneEffectCount: progressEffects.length,
usage: result.usage ?? null,
}));
return;
}
const resumedRunId = randomUUID();
const [resumedRun] = await db.insert(heartbeatRuns).values({
id: resumedRunId,