@@ -21330,13 +21330,16 @@ var privateKeyPattern = new RegExp(
2133021330 "g"
2133121331);
2133221332var SECRET_PATTERNS = [
21333+ /\bAuthorization\s*:\s*Bearer\s+([A-Za-z0-9._~+/=-]{12,})\b/gi,
21334+ /\bBearer\s+([A-Za-z0-9._~+/=-]{12,})\b/g,
2133321335 /\bsk-[A-Za-z0-9_-]{16,}\b/g,
2133421336 /\bsk-proj-[A-Za-z0-9_-]{16,}\b/g,
2133521337 /\bghp_[A-Za-z0-9_]{20,}\b/g,
2133621338 /\bgithub_pat_[A-Za-z0-9_]{20,}\b/g,
2133721339 /\bglpat-[A-Za-z0-9_-]{16,}\b/g,
2133821340 /\bxox[baprs]-[A-Za-z0-9-]{16,}\b/g,
2133921341 /\b[A-Za-z_][A-Za-z0-9_]*(?:API_KEY|TOKEN|SECRET|PASSWORD|PRIVATE_KEY)=([^\s"'`]+)\b/gi,
21342+ /\b(?:api[_-]?key|token|secret|password|private[_-]?key|authorization)\s*[:=]\s*([^\s"'`]+)\b/gi,
2134021343 privateKeyPattern
2134121344];
2134221345var SENSITIVE_ENV_KEY = /(API[_-]?KEY|SECRET|PASSWORD|PRIVATE[_-]?KEY|COOKIE|CREDENTIAL|AUTH|BEARER|(^|[_-])TOKEN$|ACCESS[_-]?TOKEN|REFRESH[_-]?TOKEN|OAUTH[_-]?TOKEN|SESSION[_-]?(KEY|TOKEN|SECRET|COOKIE))/i;
@@ -21529,7 +21532,12 @@ function writeDefaultLog(line) {
2152921532 try {
2153021533 mkdirSync(path2.dirname(logFile), { recursive: true });
2153121534 try {
21532- if (statSync2(logFile).size > logFileMaxBytes()) renameSync(logFile, `${logFile}.1`);
21535+ if (statSync2(logFile).size > logFileMaxBytes()) {
21536+ chmodSync(logFile, 384);
21537+ const rotated = `${logFile}.1`;
21538+ renameSync(logFile, rotated);
21539+ chmodSync(rotated, 384);
21540+ }
2153321541 } catch (error2) {
2153421542 if (error2?.code !== "ENOENT") {
2153521543 lastLogFileError = error2 instanceof Error ? error2.message : String(error2);
@@ -21727,11 +21735,6 @@ function codexSubagentConfigOverrides(definitions = []) {
2172721735 definition.reasoningEffort
2172821736 );
2172921737 appendConfigOverride(overrides, `${prefix}.sandbox_mode`, definition.sandbox);
21730- appendConfigOverride(overrides, `${prefix}.mcp_servers`, definition.mcpServers);
21731- appendConfigOverride(overrides, `${prefix}.skills.config`, definition.skillsConfig);
21732- for (const [key, value] of Object.entries(definition.extraConfig ?? {})) {
21733- appendConfigOverride(overrides, `${prefix}.${tomlPathSegment(key)}`, value);
21734- }
2173521738 }
2173621739 return overrides;
2173721740}
@@ -22770,6 +22773,8 @@ function recordDiagnosticEvent(event, env = process.env) {
2277022773 id: makeId(),
2277122774 ts: (/* @__PURE__ */ new Date()).toISOString(),
2277222775 ...event,
22776+ message: redactSensitiveText(event.message),
22777+ recovery: event.recovery === void 0 ? void 0 : redactJsonValue(event.recovery),
2277322778 detail: event.detail === void 0 ? void 0 : redactJsonValue(event.detail)
2277422779 };
2277522780 events.push(entry);
@@ -23584,11 +23589,29 @@ function compactJobSnapshotForMcp(job) {
2358423589 partial: isPartial(job.partial) ? compactPartialForMcp(job.partial) : compactUnknown(job.partial, 2e3)
2358523590 };
2358623591}
23592+ function compactSessionTurn(value) {
23593+ if (!value || typeof value !== "object") return value;
23594+ const turn = value;
23595+ if (typeof turn.prompt !== "string") return value;
23596+ const prompt = truncateString(turn.prompt, 2e3);
23597+ return {
23598+ ...turn,
23599+ prompt: prompt.text,
23600+ promptOmittedChars: prompt.omittedChars || void 0
23601+ };
23602+ }
23603+ function compactSessionTurns(value) {
23604+ if (!Array.isArray(value)) return value;
23605+ return value.slice(0, 20).map(compactSessionTurn);
23606+ }
2358723607function compactSessionSnapshotForMcp(session) {
2358823608 return {
2358923609 ...session,
2359023610 lastResult: compactRunValue(session.lastResult),
23591- partial: isPartial(session.partial) ? compactPartialForMcp(session.partial) : compactUnknown(session.partial, 2e3)
23611+ partial: isPartial(session.partial) ? compactPartialForMcp(session.partial) : compactUnknown(session.partial, 2e3),
23612+ activeTurn: compactSessionTurn(session.activeTurn),
23613+ queuedTurns: compactSessionTurns(session.queuedTurns),
23614+ recentTurns: compactSessionTurns(session.recentTurns)
2359223615 };
2359323616}
2359423617function compactRunValue(value) {
@@ -23916,12 +23939,18 @@ var CodexAppServerSession = class _CodexAppServerSession {
2391623939 };
2391723940 }
2391823941 async readThread(includeTurns = false) {
23919- const response = await this.request("thread/read", {
23920- threadId: this.threadId,
23921- includeTurns
23922- }, 1e4);
23923- this.capabilities.threadRead = true;
23924- return response;
23942+ try {
23943+ const response = await this.request("thread/read", {
23944+ threadId: this.threadId,
23945+ includeTurns
23946+ }, 1e4);
23947+ this.capabilities.threadRead = true;
23948+ return response;
23949+ } catch (error2) {
23950+ this.capabilities.threadRead = false;
23951+ this.lastError = error2 instanceof Error ? error2.message : String(error2);
23952+ throw error2;
23953+ }
2392523954 }
2392623955 async startTurn(options, abortSignal, onSnapshot, turnLogContext = {}) {
2392723956 if (this.activeTurn) throw new Error(`Codex app-server already has an active turn: ${this.activeTurn.turnId}`);
@@ -24517,7 +24546,7 @@ var CodexAppServerSession = class _CodexAppServerSession {
2451724546};
2451824547
2451924548// src/session-state.ts
24520- import { mkdirSync as mkdirSync3, readFileSync, renameSync as renameSync2, writeFileSync as writeFileSync2 } from "node:fs";
24549+ import { chmodSync as chmodSync2, mkdirSync as mkdirSync3, readFileSync, renameSync as renameSync2, writeFileSync as writeFileSync2 } from "node:fs";
2452124550import os6 from "node:os";
2452224551import path7 from "node:path";
2452324552function defaultSessionStateFile(env = process.env) {
@@ -24553,8 +24582,9 @@ var SessionStateStore = class {
2455324582 sessions: merged
2455424583 };
2455524584 writeFileSync2(temp, `${JSON.stringify(payload, null, 2)}
24556- `, "utf8");
24585+ `, { encoding: "utf8", mode: 384 } );
2455724586 renameSync2(temp, this.file);
24587+ chmodSync2(this.file, 384);
2455824588 }
2455924589};
2456024590function isDurableSessionState(value) {
@@ -24765,6 +24795,7 @@ var CodexSessionManager = class {
2476524795 recentTurns: [],
2476624796 draining: false,
2476724797 cancelRequested: false,
24798+ runtimeShutdownRecoverable: false,
2476824799 waiters: /* @__PURE__ */ new Set(),
2476924800 persisted: Boolean(this.stateStore),
2477024801 recovered: false,
@@ -24867,8 +24898,9 @@ var CodexSessionManager = class {
2486724898 turn.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
2486824899 this.notifyTurn(turn);
2486924900 this.notifySession(session);
24870- if (options.wait && session.activeTurn) {
24871- const completed = await this.waitForTurn(session, session.activeTurn, options.waitSignal);
24901+ const activeTurn = session.activeTurn;
24902+ if (options.wait && activeTurn) {
24903+ const completed = await this.waitForTurn(session, activeTurn, options.waitSignal);
2487224904 if (!completed) {
2487324905 return {
2487424906 session: snapshot2(session),
@@ -24881,6 +24913,7 @@ var CodexSessionManager = class {
2488124913 return {
2488224914 session: snapshot2(session),
2488324915 turn: turnSnapshot(turn),
24916+ result: activeTurn?.result,
2488424917 delivery: "delivered_to_active_turn"
2488524918 };
2488624919 }
@@ -25024,7 +25057,21 @@ var CodexSessionManager = class {
2502425057 ephemeral: false
2502525058 });
2502625059 } else {
25027- await session.appServer.readThread(false);
25060+ try {
25061+ await session.appServer.readThread(false);
25062+ } catch (error2) {
25063+ logger.warn("session.recover_thread_read_unavailable", {
25064+ sessionId: session.id,
25065+ error: errorForLog(error2)
25066+ });
25067+ recordDiagnosticEvent({
25068+ severity: "warn",
25069+ source: "session.recover",
25070+ message: error2 instanceof Error ? error2.message : String(error2),
25071+ sessionId: session.id,
25072+ detail: { protocol: session.protocol, codexThreadId: session.codexThreadId }
25073+ });
25074+ }
2502825075 }
2502925076 session.status = session.status === "failed" ? "active" : session.status;
2503025077 session.recovered = true;
@@ -25051,7 +25098,9 @@ var CodexSessionManager = class {
2505125098 const snapshots = [];
2505225099 const now = (/* @__PURE__ */ new Date()).toISOString();
2505325100 for (const session of this.sessions.values()) {
25054- session.cancelRequested = true;
25101+ const recoverable = Boolean(session.codexThreadId) && !session.cancelRequested && session.status !== "cancelled";
25102+ session.runtimeShutdownRecoverable = recoverable;
25103+ session.cancelRequested = !recoverable;
2505525104 for (const turn of session.queuedTurns) {
2505625105 turn.status = "cancelled";
2505725106 turn.updatedAt = now;
@@ -25065,7 +25114,7 @@ var CodexSessionManager = class {
2506525114 session.activeTurn.error = `Session was cancelled during ${reason}.`;
2506625115 this.notifyTurn(session.activeTurn);
2506725116 }
25068- session.status = "cancelled";
25117+ session.status = recoverable ? "active" : "cancelled";
2506925118 session.updatedAt = now;
2507025119 if (session.controller) session.controller.abort();
2507125120 if (session.appServer) closePromises.push(session.appServer.close("cancelled").catch(() => {
@@ -25074,6 +25123,12 @@ var CodexSessionManager = class {
2507425123 snapshots.push(snapshot2(session));
2507525124 }
2507625125 await Promise.allSettled(closePromises);
25126+ for (const session of this.sessions.values()) {
25127+ if (!session.runtimeShutdownRecoverable || !session.codexThreadId) continue;
25128+ session.cancelRequested = false;
25129+ session.status = "active";
25130+ session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
25131+ }
2507725132 this.persist();
2507825133 return snapshots;
2507925134 }
@@ -25188,7 +25243,7 @@ var CodexSessionManager = class {
2518825243 this.completeTurn(session, turn, result);
2518925244 return result;
2519025245 } catch (error2) {
25191- session.status = controller.signal.aborted ? "cancelled" : "failed";
25246+ session.status = controller.signal.aborted && session.runtimeShutdownRecoverable && session.codexThreadId ? "active" : controller.signal.aborted ? "cancelled" : "failed";
2519225247 session.error = error2 instanceof Error ? error2.message : String(error2);
2519325248 session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
2519425249 turn.status = controller.signal.aborted ? "cancelled" : "failed";
@@ -25315,7 +25370,7 @@ var CodexSessionManager = class {
2531525370 turn.resultStatus = result.status;
2531625371 turn.status = result.ok ? "completed" : result.status === "cancelled" ? "cancelled" : "failed";
2531725372 turn.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
25318- session.status = result.ok ? "active" : result.status === "cancelled" && session.queuedTurns.length > 0 && !session.cancelRequested ? "running" : result.status === "cancelled" ? "cancelled" : "failed";
25373+ session.status = result.ok ? "active" : result.status === "cancelled" && session.runtimeShutdownRecoverable && session.codexThreadId ? "active" : result.status === "cancelled" && session. queuedTurns.length > 0 && !session.cancelRequested ? "running" : result.status === "cancelled" ? "cancelled" : "failed";
2531925374 session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
2532025375 logger.rawInfo("session.turn.finish", {
2532125376 session: summarizeRawTrafficForLog(snapshot2(session)),
@@ -25442,6 +25497,7 @@ var CodexSessionManager = class {
2544225497 recentTurns: [],
2544325498 draining: false,
2544425499 cancelRequested: state.status === "cancelled",
25500+ runtimeShutdownRecoverable: false,
2544525501 waiters: /* @__PURE__ */ new Set(),
2544625502 persisted: true,
2544725503 recovered: true,
@@ -25694,8 +25750,8 @@ function jsonResult(value, isError = false) {
2569425750function errorResult(error2, context = "tool_call") {
2569525751 return jsonResult(
2569625752 {
25697- error: error2 instanceof Error ? error2.message : String(error2),
25698- recovery: recoveryForError(error2, context)
25753+ error: redactSensitiveText( error2 instanceof Error ? error2.message : String(error2) ),
25754+ recovery: redactJsonValue( recoveryForError(error2, context) )
2569925755 },
2570025756 true
2570125757 );
@@ -25717,6 +25773,13 @@ function withRequestAbort(options, extra) {
2571725773function requestCancelledError() {
2571825774 return new Error("MCP request was cancelled by the client.");
2571925775}
25776+ function ephemeralJobDurability() {
25777+ return {
25778+ durable: false,
25779+ survivesRestart: false,
25780+ recommendation: "Use start_codex_session_async when Claude needs recoverable long-running Codex work across MCP restarts."
25781+ };
25782+ }
2572025783function throwIfRequestAborted(extra) {
2572125784 if (extra?.signal?.aborted) throw requestCancelledError();
2572225785}
@@ -26090,7 +26153,7 @@ server.registerTool(
2609026153 "run_agent",
2609126154 {
2609226155 title: "Run one Codex agent",
26093- description: "Launch one OpenAI Codex agent via codex exec. Use automatically when the user asks Claude to use Codex, ask Codex, get a Codex second opinion, run a Codex subagent, use Codex Spark, or delegate one read-only analysis task . Defaults to the Codex desktop app binary when installed, read-only sandbox, Codex's normal service tier, and non-interactive approvals. For explicit non-sandbox/full-access requests, set dangerously_bypass_approvals_and_sandbox true.",
26156+ description: "Compatibility/manual tool for launching one OpenAI Codex agent via codex exec. Prefer ask_codex for normal Claude delegation . Defaults to the Codex desktop app binary when installed, read-only sandbox, Codex's normal service tier, and non-interactive approvals. For explicit non-sandbox/full-access requests, set dangerously_bypass_approvals_and_sandbox true.",
2609426157 inputSchema: {
2609526158 prompt: external_exports.string().min(1).describe(
2609626159 "Concrete instructions for the Codex agent. Include scope, read-only expectation, desired output shape, and file/line reference requirements when reviewing code."
@@ -26144,7 +26207,7 @@ server.registerTool(
2614426207 const job = jobManager.startAgent(toRunOptions(args));
2614526208 await progress.send(`Started Codex job ${job.id}`);
2614626209 await progress.flush();
26147- return jsonResult({ job });
26210+ return jsonResult({ job, durability: ephemeralJobDurability() });
2614826211 } catch (error2) {
2614926212 await progress.flush();
2615026213 logger.error("start_agent_run.failed", { error: errorForLog(error2) });
@@ -26286,7 +26349,7 @@ server.registerTool(
2628626349 "run_agents",
2628726350 {
2628826351 title: "Run parallel Codex agents",
26289- description: "Launch multiple independent OpenAI Codex agents concurrently and return one structured result per agent. Use automatically when the user asks for parallel Codex agents, multiple Codex subagents, broad review by independent agents, or several concurrent Codex workstreams . Split work by clear ownership, pass project_dir, keep defaults read-only, and use max_parallel to bound concurrency. For explicit non-sandbox/full-access requests, set dangerously_bypass_approvals_and_sandbox true.",
26352+ description: "Compatibility/manual tool for launching multiple independent OpenAI Codex agents concurrently. Prefer ask_codex_parallel for normal Claude delegation . Split work by clear ownership, pass project_dir, keep defaults read-only, and use max_parallel to bound concurrency. For explicit non-sandbox/full-access requests, set dangerously_bypass_approvals_and_sandbox true.",
2629026353 inputSchema: {
2629126354 agents: external_exports.array(parallelAgentSchema).min(1).max(12).describe(
2629226355 "Independent Codex agent tasks. Use names like api, tests, security, docs, performance, or ui when helpful."
@@ -26415,7 +26478,7 @@ server.registerTool(
2641526478 const job = jobManager.startAgents(toParallelRunOptions(args));
2641626479 await progress.send(`Started Codex job ${job.id}`);
2641726480 await progress.flush();
26418- return jsonResult({ job });
26481+ return jsonResult({ job, durability: ephemeralJobDurability() });
2641926482 } catch (error2) {
2642026483 await progress.flush();
2642126484 logger.error("start_agents_run.failed", { error: errorForLog(error2) });
0 commit comments