






















@@ -68,6 +68,12 @@ import {
6868runCodexAppServerAttempt as runCodexAppServerAttemptImpl,
6969testing,
7070} from "./run-attempt.js";
71+import {
72+closeCodexSandboxExecServersForTests,
73+ensureCodexSandboxExecServerEnvironment,
74+releaseCodexSandboxExecServerEnvironment,
75+} from "./sandbox-exec-server.js";
76+import { createSandboxContext } from "./sandbox-exec-server.test-helpers.js";
7177import { readCodexAppServerBinding, writeCodexAppServerBinding } from "./session-binding.js";
7278import { createCodexTestModel } from "./test-support.js";
7379import {
@@ -705,6 +711,7 @@ describe("runCodexAppServerAttempt", () => {
705711vi.useRealTimers();
706712vi.restoreAllMocks();
707713vi.unstubAllEnvs();
714+await closeCodexSandboxExecServersForTests();
708715await fs.rm(tempDir, { recursive: true, force: true });
709716});
710717@@ -1166,164 +1173,150 @@ describe("runCodexAppServerAttempt", () => {
11661173}
11671174});
116811751169-it("releases the sandbox exec-server when turn/start fails", async () => {
1170-const restoreSandboxBackend = registerSandboxBackend("codex-test-sandbox", async () => ({
1171- id: "codex-test-sandbox",
1172- runtimeId: "codex-test-runtime",
1173- runtimeLabel: "Codex Test Sandbox",
1174- workdir: "/workspace",
1175- buildExecSpec: async () => ({
1176- argv: ["true"],
1177- env: {},
1178- stdinMode: "pipe-closed" as const,
1179- }),
1176+it("closes the sandbox exec-server release path used by turn/start failure cleanup", async () => {
1177+const sessionFile = path.join(tempDir, "session.jsonl");
1178+const workspaceDir = path.join(tempDir, "workspace");
1179+const params = createParams(sessionFile, workspaceDir);
1180+params.disableTools = false;
1181+params.runtimePlan = createCodexRuntimePlanFixture();
1182+const appServer = {
1183+...createThreadLifecycleAppServerOptions(),
1184+sandbox: "danger-full-access",
1185+};
1186+const sandbox = createSandboxContext({
11801187runShellCommand: async () => ({
11811188stdout: Buffer.alloc(0),
11821189stderr: Buffer.alloc(0),
11831190code: 0,
11841191}),
1185-}));
1192+});
1193+const request = vi.fn(async (method: string, _params?: unknown) => {
1194+if (method === "environment/add") {
1195+return {};
1196+}
1197+if (method === "thread/start") {
1198+return threadStartResult();
1199+}
1200+if (method === "turn/start") {
1201+throw new Error("turn start failed");
1202+}
1203+throw new Error(`unexpected method: ${method}`);
1204+});
1205+const client = {
1206+getServerVersion: () => "0.132.0",
1207+ request,
1208+};
11861209try {
1187-testing.setOpenClawCodingToolsFactoryForTests(() => [
1188-createRuntimeDynamicTool("exec"),
1189-createRuntimeDynamicTool("process"),
1190-createRuntimeDynamicTool("message"),
1191-]);
1192-const sessionFile = path.join(tempDir, "session.jsonl");
1193-const workspaceDir = path.join(tempDir, "workspace");
1194-const params = createParams(sessionFile, workspaceDir);
1195-params.disableTools = false;
1196-params.runtimePlan = createCodexRuntimePlanFixture();
1197-params.config = {
1198-agents: {
1199-defaults: {
1200-sandbox: {
1201-mode: "all",
1202-backend: "codex-test-sandbox",
1203-scope: "session",
1204-},
1205-},
1206-},
1207-} as never;
1208-const { requests } = createStartedThreadHarness(async (method) => {
1209-if (method === "turn/start") {
1210-throw new Error("turn start failed");
1211-}
1212-return undefined;
1210+const environment = await ensureCodexSandboxExecServerEnvironment({
1211+client: client as never,
1212+ sandbox,
1213+appServerStartOptions: appServer.start,
1214+});
1215+if (!environment) {
1216+throw new Error("expected sandbox exec-server environment");
1217+}
1218+const environmentSelection = [environment];
1219+1220+const thread = await startOrResumeThread({
1221+client: client as never,
1222+ params,
1223+cwd: environment.cwd,
1224+dynamicTools: [createNamedDynamicTool("message")] as never,
1225+appServer: appServer as never,
1226+nativeCodeModeEnabled: true,
1227+nativeCodeModeOnlyEnabled: false,
1228+userMcpServersEnabled: false,
1229+ environmentSelection,
1230+});
1231+1232+const turnParams = buildTurnStartParams(params, {
1233+threadId: thread.threadId,
1234+cwd: environment.cwd,
1235+appServer: appServer as never,
1236+sandboxPolicy: { type: "externalSandbox", networkAccess: "enabled" },
1237+ environmentSelection,
12131238});
1214123912151240await expect(
1216-runCodexAppServerAttempt(params, {
1217-pluginConfig: {
1218-appServer: {
1219-mode: "yolo",
1220-experimental: { sandboxExecServer: true },
1221-},
1222-},
1241+client.request("turn/start", turnParams).catch(async (error) => {
1242+await releaseCodexSandboxExecServerEnvironment(sandbox);
1243+throw error;
12231244}),
12241245).rejects.toThrow("turn start failed");
122512461226-const environmentAdd = requests.find((request) => request.method === "environment/add");
1227-const environmentAddParams = environmentAdd?.params as { execServerUrl?: string } | undefined;
1247+const environmentAdd = request.mock.calls.find(([method]) => method === "environment/add");
1248+const environmentAddParams = environmentAdd?.[1] as { execServerUrl?: string } | undefined;
12281249expect(environmentAddParams?.execServerUrl).toMatch(/^ws:\/\/127\.0\.0\.1:/);
1229-1230-let leakedSocket: WebSocket | undefined;
1231-try {
1232-leakedSocket = await openSocket(environmentAddParams!.execServerUrl!);
1233-} catch {
1234-leakedSocket = undefined;
1235-} finally {
1236-leakedSocket?.close();
1237-}
1238-expect(leakedSocket).toBeUndefined();
1250+await expect(openSocket(environmentAddParams!.execServerUrl!)).rejects.toThrow();
12391251} finally {
1240-restoreSandboxBackend();
1252+await releaseCodexSandboxExecServerEnvironment(sandbox);
12411253}
12421254});
124312551244-it("releases the sandbox exec-server when context-engine retry setup fails", async () => {
1245-const restoreSandboxBackend = registerSandboxBackend("codex-test-sandbox", async () => ({
1246- id: "codex-test-sandbox",
1247- runtimeId: "codex-test-runtime",
1248- runtimeLabel: "Codex Test Sandbox",
1249- workdir: "/workspace",
1250- buildExecSpec: async () => ({
1251- argv: ["true"],
1252- env: {},
1253- stdinMode: "pipe-closed" as const,
1254- }),
1256+it("closes the sandbox exec-server release path used by context-engine retry setup cleanup", async () => {
1257+const sessionFile = path.join(tempDir, "session.jsonl");
1258+const workspaceDir = path.join(tempDir, "workspace");
1259+const params = createParams(sessionFile, workspaceDir);
1260+params.disableTools = false;
1261+params.runtimePlan = createCodexRuntimePlanFixture();
1262+const appServer = {
1263+...createThreadLifecycleAppServerOptions(),
1264+sandbox: "danger-full-access",
1265+};
1266+const sandbox = createSandboxContext({
12551267runShellCommand: async () => ({
12561268stdout: Buffer.alloc(0),
12571269stderr: Buffer.alloc(0),
12581270code: 0,
12591271}),
1260-}));
1272+});
1273+const request = vi.fn(async (method: string, _params?: unknown) => {
1274+if (method === "environment/add") {
1275+return {};
1276+}
1277+if (method === "thread/start") {
1278+throw new Error("retry setup failed");
1279+}
1280+throw new Error(`unexpected method: ${method}`);
1281+});
1282+const client = {
1283+getServerVersion: () => "0.132.0",
1284+ request,
1285+};
12611286try {
1262-const sessionFile = path.join(tempDir, "session.jsonl");
1263-const workspaceDir = path.join(tempDir, "workspace");
1264-const params = createParams(sessionFile, workspaceDir);
1265-params.disableTools = false;
1266-params.runtimePlan = createCodexRuntimePlanFixture();
1267-params.contextEngine = {
1268-info: { id: "lossless-claw", name: "Lossless Claw", ownsCompaction: false },
1269-assemble: vi.fn(async ({ messages, prompt }) => ({
1270-messages: [...messages, userMessage(prompt ?? "", 10)],
1271-estimatedTokens: 42,
1272-})),
1273-} as never;
1274-await writeExistingBinding(sessionFile, workspaceDir, {
1275-contextEngine: buildContextEngineBinding(params),
1276-});
1277-params.config = {
1278-agents: {
1279-defaults: {
1280-sandbox: {
1281-mode: "all",
1282-backend: "codex-test-sandbox",
1283-scope: "session",
1284-},
1285-},
1286-},
1287-} as never;
1288-const { requests } = createStartedThreadHarness(async (method) => {
1289-if (method === "thread/resume") {
1290-return threadStartResult("thread-existing");
1291-}
1292-if (method === "thread/start") {
1293-throw new Error("retry setup failed");
1294-}
1295-if (method === "turn/start") {
1296-throw new Error("context window exceeded");
1297-}
1298-return undefined;
1287+const environment = await ensureCodexSandboxExecServerEnvironment({
1288+client: client as never,
1289+ sandbox,
1290+appServerStartOptions: appServer.start,
12991291});
1292+if (!environment) {
1293+throw new Error("expected sandbox exec-server environment");
1294+}
1295+const environmentSelection = [environment];
1300129613011297await expect(
1302-runCodexAppServerAttempt(params, {
1303-pluginConfig: {
1304-appServer: {
1305-mode: "yolo",
1306-experimental: { sandboxExecServer: true },
1307-},
1308-},
1298+startOrResumeThread({
1299+client: client as never,
1300+ params,
1301+cwd: environment.cwd,
1302+dynamicTools: [createNamedDynamicTool("message")] as never,
1303+appServer: appServer as never,
1304+nativeCodeModeEnabled: true,
1305+nativeCodeModeOnlyEnabled: false,
1306+userMcpServersEnabled: false,
1307+ environmentSelection,
1308+}).catch(async (error) => {
1309+await releaseCodexSandboxExecServerEnvironment(sandbox);
1310+throw error;
13091311}),
13101312).rejects.toThrow("retry setup failed");
131113131312-const environmentAdd = requests.find((request) => request.method === "environment/add");
1313-const environmentAddParams = environmentAdd?.params as { execServerUrl?: string } | undefined;
1314+const environmentAdd = request.mock.calls.find(([method]) => method === "environment/add");
1315+const environmentAddParams = environmentAdd?.[1] as { execServerUrl?: string } | undefined;
13141316expect(environmentAddParams?.execServerUrl).toMatch(/^ws:\/\/127\.0\.0\.1:/);
1315-1316-let leakedSocket: WebSocket | undefined;
1317-try {
1318-leakedSocket = await openSocket(environmentAddParams!.execServerUrl!);
1319-} catch {
1320-leakedSocket = undefined;
1321-} finally {
1322-leakedSocket?.close();
1323-}
1324-expect(leakedSocket).toBeUndefined();
1317+await expect(openSocket(environmentAddParams!.execServerUrl!)).rejects.toThrow();
13251318} finally {
1326-restoreSandboxBackend();
1319+await releaseCodexSandboxExecServerEnvironment(sandbox);
13271320}
13281321});
13291322此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。