exec
Execute Codex CLI commands directly using plain instructions to write, run, and review code within Claude workflows, with session management support.
Instructions
Invoke Codex CLI with a plain instruction. No template, no context wrapping — just raw execution. Supports session resume.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| instruction | Yes | Instruction for Codex CLI | |
| sessionId | No | Resume an existing session by ID | |
| workingDirectory | No | Working directory path |
Implementation Reference
- src/tools/codex-exec.ts:22-129 (handler)Main handler function codexExec that implements the 'exec' tool logic - executes Codex CLI with raw instructions, supports session resume, manages progress tracking, and returns parsed results including file changes and session info
export async function codexExec( params: CodexExecParams, extra?: { signal?: AbortSignal } ): Promise<CodexWriteResult> { const config = await loadConfig({ workingDirectory: params.workingDirectory }); const toolCfg = getToolConfig(config, "exec"); const executor = await CodexExecutor.create({ workingDirectory: params.workingDirectory, }); try { const operationId = `exec-${crypto.randomUUID()}`; progressServer.startOperation(operationId, "write", params.instruction.slice(0, 120)); if (params.sessionId) { await sessionManager.updateStatus(params.sessionId, "active", { workingDirectory: params.workingDirectory, }); } let result; try { result = await executor.executeWrite(params.instruction, { sessionId: params.sessionId, workingDirectory: params.workingDirectory, model: toolCfg.model, sandbox: toolCfg.sandbox, timeout: toolCfg.timeout, onLine: (line) => { const event = mapCodexLineToProgressEvent(line, operationId); if (event) progressServer.emit(event); }, signal: extra?.signal, }); } finally { progressServer.endOperation(operationId, result?.exitCode === 0); } const parsed = executor.parseOutput(result.stdout); const sessionId = parsed.sessionId || params.sessionId; if (!sessionId) { throw { code: CodexErrorCode.CODEX_INVALID_OUTPUT, message: "Codex CLI did not return a session ID.", recoverable: false, } satisfies CodexErrorInfo; } const trackedStatus = result.exitCode !== 0 ? "abandoned" : "completed"; if (params.sessionId) { await sessionManager.markResumed(params.sessionId, { workingDirectory: params.workingDirectory, }); await sessionManager.updateStatus(params.sessionId, trackedStatus, { workingDirectory: params.workingDirectory, }); } else { await sessionManager.track({ sessionId, type: "exec", instruction: params.instruction, createdAt: new Date().toISOString(), status: trackedStatus, }, { workingDirectory: params.workingDirectory }); } let status: CodexWriteResult["status"] = "completed"; if (result.exitCode !== 0) { status = "error"; } else if (parsed.filesCreated.length > 0 || parsed.filesModified.length > 0) { status = "needs_review"; } return { success: result.exitCode === 0, sessionId, output: { summary: parsed.summary, filesModified: parsed.filesModified, filesCreated: parsed.filesCreated, }, status, }; } catch (error) { if (params.sessionId) { try { await sessionManager.updateStatus(params.sessionId, "abandoned", { workingDirectory: params.workingDirectory, }); } catch { /* best effort */ } } const errorInfo = error as CodexErrorInfo; return { success: false, sessionId: params.sessionId || "", output: { summary: "", filesModified: [], filesCreated: [] }, status: "error", error: { code: errorInfo.code || CodexErrorCode.UNKNOWN_ERROR, message: errorInfo.message || String(error), recoverable: errorInfo.recoverable ?? false, suggestion: errorInfo.suggestion, }, }; } } - src/tools/codex-exec.ts:14-20 (schema)Zod schema definition for the 'exec' tool parameters - validates instruction (required), sessionId (optional UUID), and workingDirectory (optional)
export const CodexExecParamsSchema = z.object({ instruction: z.string().describe("Instruction for Codex CLI"), sessionId: z.string().uuid().optional().describe("Resume an existing session by ID"), workingDirectory: z.string().optional().describe("Working directory path"), }); export type CodexExecParams = z.infer<typeof CodexExecParamsSchema>; - src/index.ts:54-72 (registration)MCP server registration of the 'exec' tool - defines tool name, description, input schema using zod, and maps to the codexExec handler function
// ─── exec ────────────────────────────────────────────────────────── if (isToolEnabled(config, "exec")) { server.tool( "exec", "Invoke Codex CLI with a plain instruction. No template, no context wrapping — just raw execution. Supports session resume.", { instruction: z.string().describe("Instruction for Codex CLI"), sessionId: z .string() .optional() .describe("Resume an existing session by ID"), workingDirectory: z.string().optional().describe("Working directory path"), }, async (params, extra) => { const result = await codexExec(params, extra); return { content: [ { type: "text" as const, - src/services/codex-executor.ts:43-65 (helper)executeWrite method in CodexExecutor service - handles actual Codex CLI process spawning with proper argument building for 'exec' command, including session resume support
async executeWrite( instruction: string, options: { sessionId?: string; workingDirectory?: string; model?: string; sandbox?: SandboxMode; timeout?: number; onLine?: (line: string) => void; signal?: AbortSignal; } ): Promise<ExecuteResult> { await this.checkCodexInstalled(); const { args, stdinContent } = this.buildWriteArgs(instruction, options); return this.spawnCodex(args, { cwd: options.workingDirectory, timeout: options.timeout ?? this.config.timeout, stdinContent, onLine: options.onLine, signal: options.signal, }); } - buildWriteArgs helper method - constructs CLI arguments for the 'exec' command, handling both new sessions and session resume with proper flags for model, sandbox, and JSON output
private buildWriteArgs( instruction: string, options: { sessionId?: string; model?: string; sandbox?: SandboxMode; } ): { args: string[]; stdinContent: string } { const args: string[] = []; // Always use stdin to avoid shell injection with shell: true if (options.sessionId) { args.push("exec", "resume", options.sessionId, "-"); } else { args.push("exec", "-"); } args.push("--json"); const model = options.model || this.config.model; if (model) { args.push("--model", model); } // --sandbox is only valid for new sessions, not resume if (!options.sessionId) { const sandboxMode = options.sandbox || this.config.sandbox || "danger-full-access"; args.push("--sandbox", sandboxMode); } else { // resume does not support --sandbox; bypass sandbox to allow file writes args.push("--dangerously-bypass-approvals-and-sandbox"); } return { args, stdinContent: instruction }; }