@openclaw/codex 2026.5.1-beta.1 → 2026.5.2-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/codex",
3
- "version": "2026.5.1-beta.1",
3
+ "version": "2026.5.2-beta.1",
4
4
  "description": "OpenClaw Codex harness and model provider plugin",
5
5
  "repository": {
6
6
  "type": "git",
@@ -26,14 +26,11 @@
26
26
  "defaultChoice": "npm",
27
27
  "minHostVersion": ">=2026.5.1-beta.1"
28
28
  },
29
- "bundle": {
30
- "includeInCore": false
31
- },
32
29
  "compat": {
33
- "pluginApi": ">=2026.5.1-beta.1"
30
+ "pluginApi": ">=2026.5.2-beta.1"
34
31
  },
35
32
  "build": {
36
- "openclawVersion": "2026.5.1-beta.1"
33
+ "openclawVersion": "2026.5.2-beta.1"
37
34
  },
38
35
  "release": {
39
36
  "publishToClawHub": true,
@@ -26,6 +26,7 @@ function createParams(sessionFile: string, workspaceDir: string): EmbeddedRunAtt
26
26
  disableTools: true,
27
27
  timeoutMs: 5_000,
28
28
  authStorage: {} as never,
29
+ authProfileStore: { version: 1, profiles: {} },
29
30
  modelRegistry: {} as never,
30
31
  } as EmbeddedRunAttemptParams;
31
32
  }
@@ -61,6 +61,7 @@ describe("CodexAppServerClient", () => {
61
61
  expect(warn).toHaveBeenCalledWith(
62
62
  "failed to parse codex app-server message",
63
63
  expect.objectContaining({
64
+ consoleMessage: expect.stringContaining("<redacted>"),
64
65
  linePreview: '{"token":"<redacted>"} trailing',
65
66
  }),
66
67
  ),
@@ -68,6 +69,40 @@ describe("CodexAppServerClient", () => {
68
69
  expect(JSON.stringify(warn.mock.calls)).not.toContain("secret-value");
69
70
  });
70
71
 
72
+ it("redacts prefixed env credential names from app-server previews", () => {
73
+ expect(
74
+ __testing.redactCodexAppServerLinePreview(
75
+ "fatal OPENAI_API_KEY=sk-live ANTHROPIC_API_KEY='anthropic-secret' OTHER=value",
76
+ ),
77
+ ).toBe("fatal OPENAI_API_KEY=<redacted> ANTHROPIC_API_KEY='<redacted>' OTHER=value");
78
+ });
79
+
80
+ it("recovers app-server messages split by raw newlines inside JSON strings", async () => {
81
+ const warn = vi.spyOn(embeddedAgentLog, "warn").mockImplementation(() => undefined);
82
+ const harness = createClientHarness();
83
+ clients.push(harness.client);
84
+ const notifications: unknown[] = [];
85
+ harness.client.addNotificationHandler((notification) => {
86
+ notifications.push(notification);
87
+ });
88
+
89
+ harness.process.stdout.write(
90
+ '{"method":"item/commandExecution/outputDelta","params":{"delta":"first' +
91
+ "\n" +
92
+ 'second"}}\n',
93
+ );
94
+
95
+ await vi.waitFor(() =>
96
+ expect(notifications).toEqual([
97
+ {
98
+ method: "item/commandExecution/outputDelta",
99
+ params: { delta: "first\nsecond" },
100
+ },
101
+ ]),
102
+ );
103
+ expect(warn).not.toHaveBeenCalled();
104
+ });
105
+
71
106
  it("preserves JSON-RPC error codes", async () => {
72
107
  const harness = createClientHarness();
73
108
  clients.push(harness.client);
@@ -278,9 +313,23 @@ describe("CodexAppServerClient", () => {
278
313
  // an unhandled exception tearing down the gateway.
279
314
  await expect(pending).rejects.toThrow("write EPIPE");
280
315
 
281
- // Subsequent requests are rejected immediately (client is closed).
316
+ // Subsequent requests keep the original close reason so startup logs stay actionable.
317
+ await expect(harness.client.request("another/method")).rejects.toThrow("write EPIPE");
318
+ });
319
+
320
+ it("preserves redacted app-server stderr on exit errors", async () => {
321
+ const harness = createClientHarness();
322
+ clients.push(harness.client);
323
+
324
+ const pending = harness.client.request("test/method");
325
+ harness.process.stderr.write('fatal token="secret-value" while booting\n');
326
+ harness.process.emit("exit", 1, null);
327
+
328
+ await expect(pending).rejects.toThrow(
329
+ 'codex app-server exited: code=1 signal=null stderr="fatal token=\\"<redacted>\\" while booting"',
330
+ );
282
331
  await expect(harness.client.request("another/method")).rejects.toThrow(
283
- "codex app-server client is closed",
332
+ "codex app-server exited: code=1 signal=null",
284
333
  );
285
334
  });
286
335
 
@@ -25,7 +25,10 @@ import { MIN_CODEX_APP_SERVER_VERSION } from "./version.js";
25
25
 
26
26
  export { MIN_CODEX_APP_SERVER_VERSION } from "./version.js";
27
27
  const CODEX_APP_SERVER_PARSE_LOG_MAX = 500;
28
+ const CODEX_APP_SERVER_PARSE_BUFFER_MAX = 1_000_000;
29
+ const CODEX_APP_SERVER_PARSE_BUFFER_MAX_LINES = 1_000;
28
30
  const CODEX_DYNAMIC_TOOL_SERVER_REQUEST_TIMEOUT_MS = 30_000;
31
+ const CODEX_APP_SERVER_STDERR_TAIL_MAX = 2_000;
29
32
 
30
33
  type PendingRequest = {
31
34
  method: string;
@@ -46,6 +49,16 @@ export class CodexAppServerRpcError extends Error {
46
49
  }
47
50
  }
48
51
 
52
+ export function isCodexAppServerConnectionClosedError(error: unknown): boolean {
53
+ if (!(error instanceof Error)) {
54
+ return false;
55
+ }
56
+ return (
57
+ error.message === "codex app-server client is closed" ||
58
+ error.message.startsWith("codex app-server exited:")
59
+ );
60
+ }
61
+
49
62
  type CodexServerRequestHandler = (
50
63
  request: Required<Pick<RpcRequest, "id" | "method">> & { params?: JsonValue },
51
64
  ) => Promise<JsonValue | undefined> | JsonValue | undefined;
@@ -64,26 +77,33 @@ export class CodexAppServerClient {
64
77
  private nextId = 1;
65
78
  private initialized = false;
66
79
  private closed = false;
80
+ private closeError: Error | undefined;
81
+ private stderrTail = "";
82
+ private pendingParse:
83
+ | {
84
+ text: string;
85
+ lineCount: number;
86
+ firstError: unknown;
87
+ }
88
+ | undefined;
67
89
 
68
90
  private constructor(child: CodexAppServerTransport) {
69
91
  this.child = child;
70
92
  this.lines = createInterface({ input: child.stdout });
71
93
  this.lines.on("line", (line) => this.handleLine(line));
72
94
  child.stderr.on("data", (chunk: Buffer | string) => {
73
- const text = chunk.toString("utf8").trim();
74
- if (text) {
75
- embeddedAgentLog.debug(`codex app-server stderr: ${text}`);
95
+ const text = chunk.toString("utf8");
96
+ this.stderrTail = appendBoundedTail(this.stderrTail, text, CODEX_APP_SERVER_STDERR_TAIL_MAX);
97
+ const trimmed = text.trim();
98
+ if (trimmed) {
99
+ embeddedAgentLog.debug(`codex app-server stderr: ${trimmed}`);
76
100
  }
77
101
  });
78
102
  child.once("error", (error) =>
79
103
  this.closeWithError(error instanceof Error ? error : new Error(String(error))),
80
104
  );
81
105
  child.once("exit", (code, signal) => {
82
- this.closeWithError(
83
- new Error(
84
- `codex app-server exited: code=${formatExitValue(code)} signal=${formatExitValue(signal)}`,
85
- ),
86
- );
106
+ this.closeWithError(buildCodexAppServerExitError(code, signal, this.stderrTail));
87
107
  });
88
108
  // Guard against unhandled EPIPE / write-after-close errors on the stdin
89
109
  // stream. When the child process terminates abruptly the pipe can break
@@ -152,7 +172,7 @@ export class CodexAppServerClient {
152
172
  ): Promise<T> {
153
173
  options ??= {};
154
174
  if (this.closed) {
155
- return Promise.reject(new Error("codex app-server client is closed"));
175
+ return Promise.reject(this.closeError ?? new Error("codex app-server client is closed"));
156
176
  }
157
177
  if (options.signal?.aborted) {
158
178
  return Promise.reject(new Error(`${method} aborted`));
@@ -262,7 +282,12 @@ export class CodexAppServerClient {
262
282
  }
263
283
 
264
284
  private handleLine(line: string): void {
265
- const trimmed = line.trim();
285
+ const rawLine = line.endsWith("\r") ? line.slice(0, -1) : line;
286
+ if (this.pendingParse) {
287
+ this.handlePendingParseLine(rawLine);
288
+ return;
289
+ }
290
+ const trimmed = rawLine.trim();
266
291
  if (!trimmed) {
267
292
  return;
268
293
  }
@@ -270,12 +295,43 @@ export class CodexAppServerClient {
270
295
  try {
271
296
  parsed = JSON.parse(trimmed);
272
297
  } catch (error) {
273
- embeddedAgentLog.warn("failed to parse codex app-server message", {
274
- error,
275
- linePreview: redactCodexAppServerLinePreview(trimmed),
276
- });
298
+ if (shouldBufferCodexAppServerParseFailure(trimmed, error)) {
299
+ this.pendingParse = { text: trimmed, lineCount: 1, firstError: error };
300
+ return;
301
+ }
302
+ logCodexAppServerParseFailure(trimmed, error, 1);
303
+ return;
304
+ }
305
+ this.handleParsedMessage(parsed);
306
+ }
307
+
308
+ private handlePendingParseLine(line: string): void {
309
+ const pending = this.pendingParse;
310
+ if (!pending) {
311
+ return;
312
+ }
313
+ const candidate = `${pending.text}\\n${line}`;
314
+ let parsed: unknown;
315
+ try {
316
+ parsed = JSON.parse(candidate);
317
+ } catch (error) {
318
+ const lineCount = pending.lineCount + 1;
319
+ if (
320
+ candidate.length <= CODEX_APP_SERVER_PARSE_BUFFER_MAX &&
321
+ lineCount <= CODEX_APP_SERVER_PARSE_BUFFER_MAX_LINES
322
+ ) {
323
+ this.pendingParse = { text: candidate, lineCount, firstError: pending.firstError };
324
+ return;
325
+ }
326
+ this.pendingParse = undefined;
327
+ logCodexAppServerParseFailure(candidate, error, lineCount);
277
328
  return;
278
329
  }
330
+ this.pendingParse = undefined;
331
+ this.handleParsedMessage(parsed);
332
+ }
333
+
334
+ private handleParsedMessage(parsed: unknown): void {
279
335
  if (!parsed || typeof parsed !== "object") {
280
336
  return;
281
337
  }
@@ -396,6 +452,7 @@ export class CodexAppServerClient {
396
452
  return false;
397
453
  }
398
454
  this.closed = true;
455
+ this.closeError = error;
399
456
  this.lines.close();
400
457
  this.rejectPendingRequests(error);
401
458
  return true;
@@ -541,12 +598,55 @@ function redactCodexAppServerLinePreview(value: string): string {
541
598
  .replace(
542
599
  /("(?:api_?key|authorization|token|access_token|refresh_token)"\s*:\s*")([^"]+)(")/gi,
543
600
  "$1<redacted>$3",
601
+ )
602
+ .replace(
603
+ /\b([a-z0-9_]*(?:api_?key|authorization|access_token|refresh_token|token))(\s*=\s*)(["']?)[^\s"']+(\3)/gi,
604
+ "$1$2$3<redacted>$4",
544
605
  );
545
606
  return redacted.length > CODEX_APP_SERVER_PARSE_LOG_MAX
546
607
  ? `${redacted.slice(0, CODEX_APP_SERVER_PARSE_LOG_MAX)}...`
547
608
  : redacted;
548
609
  }
549
610
 
611
+ function appendBoundedTail(current: string, next: string, maxLength: number): string {
612
+ const combined = `${current}${next}`;
613
+ return combined.length > maxLength ? combined.slice(combined.length - maxLength) : combined;
614
+ }
615
+
616
+ function buildCodexAppServerExitError(code: unknown, signal: unknown, stderrTail: string): Error {
617
+ const stderrPreview = redactCodexAppServerLinePreview(stderrTail);
618
+ const suffix = stderrPreview ? ` stderr=${JSON.stringify(stderrPreview)}` : "";
619
+ return new Error(
620
+ `codex app-server exited: code=${formatExitValue(code)} signal=${formatExitValue(
621
+ signal,
622
+ )}${suffix}`,
623
+ );
624
+ }
625
+
626
+ function shouldBufferCodexAppServerParseFailure(value: string, error: unknown): boolean {
627
+ if (!value.startsWith("{") && !value.startsWith("[")) {
628
+ return false;
629
+ }
630
+ const message = error instanceof Error ? error.message : String(error);
631
+ return (
632
+ message.includes("Unterminated string") || message.includes("Unexpected end of JSON input")
633
+ );
634
+ }
635
+
636
+ function logCodexAppServerParseFailure(value: string, error: unknown, fragmentCount: number): void {
637
+ const linePreview = redactCodexAppServerLinePreview(value);
638
+ const suffix = fragmentCount > 1 ? ` fragments=${fragmentCount}` : "";
639
+ embeddedAgentLog.warn("failed to parse codex app-server message", {
640
+ error,
641
+ errorMessage: error instanceof Error ? error.message : String(error),
642
+ fragmentCount,
643
+ linePreview,
644
+ consoleMessage: `failed to parse codex app-server message${suffix}: preview=${JSON.stringify(
645
+ linePreview,
646
+ )}`,
647
+ });
648
+ }
649
+
550
650
  const CODEX_APP_SERVER_APPROVAL_REQUEST_METHODS = new Set([
551
651
  "item/commandExecution/requestApproval",
552
652
  "item/fileChange/requestApproval",
@@ -74,6 +74,7 @@ export async function maybeCompactCodexAppServerSession(
74
74
  sessionFile: params.sessionFile,
75
75
  reason: "compaction",
76
76
  runtimeContext: params.contextEngineRuntimeContext,
77
+ config: params.config,
77
78
  });
78
79
  } catch (error) {
79
80
  embeddedAgentLog.warn(
@@ -0,0 +1,31 @@
1
+ import type { CodexPluginConfig } from "./config.js";
2
+
3
+ export const CODEX_NATIVE_FIRST_DYNAMIC_TOOL_EXCLUDES = [
4
+ "read",
5
+ "write",
6
+ "edit",
7
+ "apply_patch",
8
+ "exec",
9
+ "process",
10
+ "update_plan",
11
+ ] as const;
12
+
13
+ export function applyCodexDynamicToolProfile<T extends { name: string }>(
14
+ tools: T[],
15
+ config: Pick<CodexPluginConfig, "codexDynamicToolsProfile" | "codexDynamicToolsExclude">,
16
+ ): T[] {
17
+ const excludes = new Set<string>();
18
+ const profile = config.codexDynamicToolsProfile ?? "native-first";
19
+ if (profile === "native-first") {
20
+ for (const name of CODEX_NATIVE_FIRST_DYNAMIC_TOOL_EXCLUDES) {
21
+ excludes.add(name);
22
+ }
23
+ }
24
+ for (const name of config.codexDynamicToolsExclude ?? []) {
25
+ const trimmed = name.trim();
26
+ if (trimmed) {
27
+ excludes.add(trimmed);
28
+ }
29
+ }
30
+ return excludes.size === 0 ? tools : tools.filter((tool) => !excludes.has(tool.name));
31
+ }
@@ -220,6 +220,7 @@ export class CodexAppServerEventProjector {
220
220
  timedOut: false,
221
221
  idleTimedOut: false,
222
222
  timedOutDuringCompaction: false,
223
+ timedOutDuringToolExecution: false,
223
224
  promptError,
224
225
  promptErrorSource: promptError ? this.promptErrorSource || "prompt" : null,
225
226
  sessionIdUsed: this.params.sessionId,
@@ -1,7 +1,10 @@
1
+ import { mkdir, mkdtemp, realpath, writeFile } from "node:fs/promises";
2
+ import os from "node:os";
1
3
  import path from "node:path";
2
4
  import { describe, expect, it, vi } from "vitest";
3
5
  import type { CodexAppServerStartOptions } from "./config.js";
4
6
  import {
7
+ __testing,
5
8
  resolveManagedCodexAppServerPaths,
6
9
  resolveManagedCodexAppServerStartOptions,
7
10
  } from "./managed-binary.js";
@@ -64,6 +67,16 @@ describe("managed Codex app-server binary", () => {
64
67
  );
65
68
  });
66
69
 
70
+ it("uses the package root when the resolver is bundled into a dist chunk", () => {
71
+ expect(__testing.resolveDefaultCodexPluginRoot("/repo/openclaw/dist")).toBe("/repo/openclaw");
72
+ expect(__testing.resolveDefaultCodexPluginRoot("/repo/openclaw/dist-runtime")).toBe(
73
+ "/repo/openclaw",
74
+ );
75
+ expect(
76
+ __testing.resolveDefaultCodexPluginRoot("/repo/openclaw/extensions/codex/src/app-server"),
77
+ ).toBe("/repo/openclaw/extensions/codex");
78
+ });
79
+
67
80
  it("finds Codex in the package install root used by packaged plugins", async () => {
68
81
  const installRoot = path.join("/tmp", "openclaw-plugin-package", "codex");
69
82
  const pluginRoot = path.join(installRoot, "dist", "extensions", "codex");
@@ -83,6 +96,39 @@ describe("managed Codex app-server binary", () => {
83
96
  });
84
97
  });
85
98
 
99
+ it("falls back to the resolved Codex package bin when no command shim exists", async () => {
100
+ const installRoot = await mkdtemp(path.join(os.tmpdir(), "openclaw-codex-package-"));
101
+ const pluginRoot = path.join(installRoot, "dist", "extensions", "codex");
102
+ const packageRoot = path.join(installRoot, "node_modules", "@openai", "codex");
103
+ const packageBin = path.join(packageRoot, "bin", "codex.js");
104
+ await mkdir(path.dirname(packageBin), { recursive: true });
105
+ await writeFile(
106
+ path.join(packageRoot, "package.json"),
107
+ JSON.stringify({
108
+ name: "@openai/codex",
109
+ bin: {
110
+ codex: "bin/codex.js",
111
+ },
112
+ }),
113
+ );
114
+ await writeFile(packageBin, "#!/usr/bin/env node\n");
115
+ const resolvedPackageBin = await realpath(packageBin);
116
+
117
+ const pathExists = vi.fn(async (filePath: string) => filePath === resolvedPackageBin);
118
+
119
+ await expect(
120
+ resolveManagedCodexAppServerStartOptions(startOptions("managed"), {
121
+ platform: "linux",
122
+ pluginRoot,
123
+ pathExists,
124
+ }),
125
+ ).resolves.toEqual({
126
+ ...startOptions("managed"),
127
+ command: resolvedPackageBin,
128
+ commandSource: "resolved-managed",
129
+ });
130
+ });
131
+
86
132
  it("fails clearly when the managed Codex binary is missing", async () => {
87
133
  await expect(
88
134
  resolveManagedCodexAppServerStartOptions(startOptions("managed"), {
@@ -1,11 +1,13 @@
1
- import { constants as fsConstants } from "node:fs";
1
+ import { constants as fsConstants, readFileSync } from "node:fs";
2
2
  import { access } from "node:fs/promises";
3
+ import { createRequire } from "node:module";
3
4
  import path from "node:path";
4
5
  import { fileURLToPath } from "node:url";
5
6
  import type { CodexAppServerStartOptions } from "./config.js";
6
7
  import { MANAGED_CODEX_APP_SERVER_PACKAGE } from "./version.js";
7
8
 
8
- const CODEX_PLUGIN_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
9
+ const CODEX_APP_SERVER_MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
10
+ const CODEX_PLUGIN_ROOT = resolveDefaultCodexPluginRoot(CODEX_APP_SERVER_MODULE_DIR);
9
11
 
10
12
  type ManagedCodexAppServerPaths = {
11
13
  commandPath: string;
@@ -66,7 +68,29 @@ function resolveManagedCodexAppServerCommandCandidates(
66
68
  ): string[] {
67
69
  const pathApi = pathForPlatform(platform);
68
70
  const commandName = platform === "win32" ? "codex.cmd" : "codex";
69
- const roots = [
71
+ const roots = resolveManagedCodexAppServerCandidateRoots(pluginRoot, platform);
72
+ return [
73
+ ...new Set([
74
+ ...roots.map((root) => pathApi.join(root, "node_modules", ".bin", commandName)),
75
+ ...resolveManagedCodexPackageBinCandidates(roots, platform),
76
+ ]),
77
+ ];
78
+ }
79
+
80
+ function resolveDefaultCodexPluginRoot(moduleDir: string): string {
81
+ const moduleBaseName = path.basename(moduleDir);
82
+ if (moduleBaseName === "dist" || moduleBaseName === "dist-runtime") {
83
+ return path.dirname(moduleDir);
84
+ }
85
+ return path.resolve(moduleDir, "..", "..");
86
+ }
87
+
88
+ function resolveManagedCodexAppServerCandidateRoots(
89
+ pluginRoot: string,
90
+ platform: NodeJS.Platform,
91
+ ): string[] {
92
+ const pathApi = pathForPlatform(platform);
93
+ return [
70
94
  pluginRoot,
71
95
  pathApi.dirname(pluginRoot),
72
96
  pathApi.dirname(pathApi.dirname(pluginRoot)),
@@ -74,9 +98,56 @@ function resolveManagedCodexAppServerCommandCandidates(
74
98
  ? pathApi.dirname(pathApi.dirname(pathApi.dirname(pluginRoot)))
75
99
  : null,
76
100
  ].filter((root): root is string => Boolean(root));
77
- return [...new Set(roots.map((root) => pathApi.join(root, "node_modules", ".bin", commandName)))];
78
101
  }
79
102
 
103
+ function resolveManagedCodexPackageBinCandidates(
104
+ roots: readonly string[],
105
+ platform: NodeJS.Platform,
106
+ ): string[] {
107
+ if (platform === "win32") {
108
+ return [];
109
+ }
110
+
111
+ const candidates: string[] = [];
112
+ for (const root of roots) {
113
+ const candidate = resolveManagedCodexPackageBinCandidate(root);
114
+ if (candidate) {
115
+ candidates.push(candidate);
116
+ }
117
+ }
118
+ return candidates;
119
+ }
120
+
121
+ function resolveManagedCodexPackageBinCandidate(root: string): string | null {
122
+ try {
123
+ const requireFromRoot = createRequire(path.join(root, "package.json"));
124
+ const packageJsonPath = requireFromRoot.resolve(
125
+ `${MANAGED_CODEX_APP_SERVER_PACKAGE}/package.json`,
126
+ );
127
+ const packageRoot = path.dirname(packageJsonPath);
128
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8")) as {
129
+ bin?: unknown;
130
+ };
131
+ const binPath =
132
+ typeof packageJson.bin === "string"
133
+ ? packageJson.bin
134
+ : isRecord(packageJson.bin) && typeof packageJson.bin.codex === "string"
135
+ ? packageJson.bin.codex
136
+ : null;
137
+ return binPath ? path.resolve(packageRoot, binPath) : null;
138
+ } catch {
139
+ return null;
140
+ }
141
+ }
142
+
143
+ function isRecord(value: unknown): value is Record<string, unknown> {
144
+ return typeof value === "object" && value !== null;
145
+ }
146
+
147
+ export const __testing = {
148
+ resolveDefaultCodexPluginRoot,
149
+ };
150
+
80
151
  function isDistExtensionRoot(pluginRoot: string, platform: NodeJS.Platform): boolean {
81
152
  const pathApi = pathForPlatform(platform);
82
153
  const extensionsDir = pathApi.dirname(pluginRoot);
@@ -30,6 +30,7 @@ function createParams(sessionFile: string, workspaceDir: string): EmbeddedRunAtt
30
30
  disableTools: true,
31
31
  timeoutMs: 5_000,
32
32
  authStorage: {} as never,
33
+ authProfileStore: { version: 1, profiles: {} },
33
34
  modelRegistry: {} as never,
34
35
  } as EmbeddedRunAttemptParams;
35
36
  }
@@ -212,6 +213,7 @@ describe("runCodexAppServerAttempt context-engine lifecycle", () => {
212
213
  SessionManager.open(sessionFile).appendMessage(
213
214
  assistantMessage("existing context", Date.now()) as never,
214
215
  );
216
+ const openSpy = vi.spyOn(SessionManager, "open");
215
217
  const contextEngine = createContextEngine();
216
218
  const harness = createStartedThreadHarness();
217
219
  const params = createParams(sessionFile, workspaceDir);
@@ -265,6 +267,7 @@ describe("runCodexAppServerAttempt context-engine lifecycle", () => {
265
267
 
266
268
  await harness.completeTurn();
267
269
  await run;
270
+ expect(openSpy).not.toHaveBeenCalled();
268
271
  });
269
272
 
270
273
  it("calls afterTurn with the mirrored transcript and runs turn maintenance", async () => {