Codex SDK Automation skill for Codex
Drive Codex from TypeScript with real guardrails. Codex writes SDK integrations that set working directory, sandbox, approval policy, timeout and output schema on every thread, stream events for logging, verify results outside the agent, and record thread ids and token usage, so batch jobs and internal tools fail loudly and can be resumed instead of hanging or silently succeeding.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- codex-sdk-automation Installs to ~/.agents/skills/codex-sdk-automation/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install codex-sdk-automation The CLI also searches, updates and removes skills. Run it with no arguments to pick from a list. Needs Node 20.12+.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- --project codex-sdk-automation Installs to .agents/skills/codex-sdk-automation/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/codex-sdk-automation.zipunzip -o codex-sdk-automation.zip -d ~/.agents/skills/ The archive holds the skill folder with its generated agents/openai.yaml. Unzip into .agents/skills/ to scope it to a repo.
& ([scriptblock]::Create((irm codexskills.dev/install.ps1))) codex-sdk-automation PowerShell 5.1 or 7. Installs to %USERPROFILE%\.agents\skills\. Add -Project for the current repo.
104-line POSIX script. No sudo, no telemetry. Read install.sh
Use it in Codex
Start a prompt with $codex-sdk-automation, or pick it from /skills.
You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.
What Codex reads before loading it
Scripts Codex from Node.js with the @openai/codex-sdk TypeScript library, covering threads, run versus runStreamed, per-turn outputSchema, AbortSignal timeouts, sandbox and approval thread options, env isolation, resumeThread and token usage, for batch jobs, internal tools and CI pipelines. Use when the user wants to call Codex from TypeScript or JavaScript, build an agent pipeline around Codex, process many repos or tasks programmatically, or replace a fragile shell loop around codex exec.
Use it when
- Calling Codex from TypeScript or JavaScript code
- Building a batch job or internal tool around Codex turns
- You need streamed progress, cancellation or multi-turn threads in a program
- A shell loop around codex exec has grown branching and retries
Use something else when
- A single headless run in a CI step is enough: use codex-exec-ci
- The main question is the JSON result shape: use codex-output-schemas
How to check the result
Run one task end to end: finalResponse, items and usage are logged, the abort timeout fires on a forced hang, and a failing task yields a recorded error and non-zero exit.
The mistake it prevents
Passing a custom env without PATH and HOME, so the spawned Codex CLI cannot find tools or its own config.
An SDK integration is done well when every run has an explicit working directory, sandbox, approval policy, timeout and output contract; failures surface as thrown errors or non-zero exits; and thread ids and token usage are recorded so runs can be resumed and costed.
Choose the integration
| Need | Use |
|---|---|
| One-off scripted run from a shell or CI step | codex exec (see codex-exec-ci) |
| Program logic around Codex turns: loops, branching, retries, parsing | @openai/codex-sdk (this skill) |
| Python service | openai-codex Python SDK (pip install openai-codex, Python 3.10+) |
| Custom client UI with your own approvals and history | Codex app server protocol |
The TypeScript SDK spawns the Codex CLI and exchanges JSONL events with it, so the same config layers, AGENTS.md discovery, sandbox and auth apply. It needs Node.js 18+ and runs server-side only.
Setup
npm install @openai/codex-sdk
Auth: reuse the machine’s saved Codex login, or pass apiKey (sent to the CLI as
CODEX_API_KEY). In CI, read the key from the secret store at the call site, never
from a job-wide environment variable that build scripts can read.
Core API
import { Codex } from "@openai/codex-sdk";
const codex = new Codex({
apiKey: process.env.CODEX_API_KEY, // optional; omit to use saved login
config: { sandbox_workspace_write: { network_access: false } }, // flattened to --config
});
const thread = codex.startThread({
workingDirectory: "/srv/checkouts/payments",
sandboxMode: "workspace-write", // read-only | workspace-write | danger-full-access
approvalPolicy: "never", // nobody is there to approve
model: "gpt-6.1-sol", // omit to use the configured default
modelReasoningEffort: "medium",
});
const turn = await thread.run("Run the tests, fix the failing one, rerun the tests.");
console.log(turn.finalResponse); // last agent message
console.log(turn.items); // commands, file changes, MCP calls, todo lists...
console.log(turn.usage); // input, cached_input, output, reasoning_output tokens
console.log(thread.id); // persist this to resume later
thread.run()again continues the same thread with full prior context.codex.resumeThread(id)rebuilds a thread from~/.codex/sessionsafter a restart.run()throws when the turn fails; catch it per task so one failure does not stop the batch.
Thread and client options
| Option | Where | Purpose |
|---|---|---|
workingDirectory | thread | Repo to operate on; must be a git repo unless skipGitRepoCheck: true |
sandboxMode | thread | Filesystem boundary for commands |
approvalPolicy | thread | Use "never" for unattended runs; "on-request" has nobody to answer |
networkAccessEnabled | thread | Command network access in workspace-write |
webSearchMode | thread | "disabled", "cached" or "live" |
additionalDirectories | thread | Extra writable roots |
model, modelReasoningEffort | thread | Per-thread model and effort |
config / configOverrides | client | Any config key; raw TOML strings for keys dotted paths cannot express |
env | client | Full environment for the CLI; when set, process.env is NOT inherited |
codexPathOverride | client | Pin a specific codex binary |
Per-turn options
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 15 * 60_000);
try {
const turn = await thread.run(prompt, { outputSchema: schema, signal: controller.signal });
} finally {
clearTimeout(timer);
}
outputSchema makes the final message JSON matching the schema (strict structured
output rules apply; see codex-output-schemas). Parse finalResponse with JSON.parse
and validate it anyway.
Streaming
Use runStreamed() when you need progress, early cancellation or per-item logging:
const { events } = await thread.runStreamed("Diagnose the flaky test in tests/sync");
for await (const ev of events) {
if (ev.type === "item.completed" && ev.item.type === "command_execution")
log.info({ cmd: ev.item.command, exit: ev.item.exit_code });
if (ev.type === "item.completed" && ev.item.type === "file_change")
log.info({ files: ev.item.changes.map((c) => `${c.kind} ${c.path}`) });
if (ev.type === "turn.completed") log.info({ usage: ev.usage });
if (ev.type === "turn.failed") throw new Error(ev.error.message);
}
Event types: thread.started, turn.started, turn.completed, turn.failed,
item.started, item.updated, item.completed, error. Item types:
agent_message, reasoning, command_execution, file_change, mcp_tool_call,
web_search, todo_list, error.
Batch pipeline pattern
- [ ] 1. Prepare one isolated checkout per task (fresh clone or git worktree)
- [ ] 2. Start a thread per task with explicit options
- [ ] 3. Run with a timeout and an output schema
- [ ] 4. Verify outside the agent (tests, git diff)
- [ ] 5. Record thread id, usage, verdict; continue on failure
- [ ] 6. Exit non-zero if any task failed
import { spawnSync } from "node:child_process";
import { writeFileSync } from "node:fs";
const results = [];
for (const task of tasks) { // run sequentially, or with a small pool (2-4)
const thread = codex.startThread({ workingDirectory: task.dir, sandboxMode: "workspace-write",
approvalPolicy: "never" });
try {
const turn = await thread.run(task.prompt, { outputSchema, signal: AbortSignal.timeout(900_000) });
const out = JSON.parse(turn.finalResponse);
const testsOk = spawnSync("npm", ["test"], { cwd: task.dir }).status === 0; // trust, but verify
results.push({ id: task.id, thread: thread.id, usage: turn.usage, out, testsOk });
} catch (err) {
results.push({ id: task.id, thread: thread.id, error: String(err) });
}
}
writeFileSync("codex-results.json", JSON.stringify(results, null, 2));
process.exit(results.some((r) => r.error || r.testsOk === false) ? 1 : 0);
Keep concurrency low: each thread is a CLI process plus model calls, and parallel threads in the same checkout will overwrite each other.
Gotchas
- Passing
envreplaces the whole environment. IncludePATHandHOME(andCODEX_HOMEif you use one) or the CLI cannot find tools or its config. approvalPolicy: "on-request"in an unattended job means requests that need approval cannot be granted. Choose the sandbox that makes approvals unnecessary.danger-full-accessbelongs only in a disposable container or VM; never on a developer laptop or shared runner with credentials.thread.idis only known after the first turn starts; store it afterrun()or on thethread.startedevent.- The SDK inherits the user’s
~/.codex/config.toml, AGENTS.md files and MCP servers. For reproducible jobs, setCODEX_HOMEto a job-specific directory with a minimal config. - Retired model ids fail at runtime; read the model from configuration rather than hard-coding it across scripts.
- Do not let the agent push, merge or deploy from a pipeline; have the pipeline do those steps after verification, gated on human approval for anything irreversible.
Verification
- A dry run on one task prints
finalResponse, a non-emptyitemslist andusage. - Killing a run with the abort signal leaves the job reporting a timeout, not hanging.
- A deliberately failing task produces a recorded error and a non-zero exit while the other tasks still complete.
codex resume <thread id>opens a recorded thread for inspection.
What the zip contains
SKILL.md plus the interface file below. Nothing runs on install.
agents/openai.yaml
Gives the Codex app a display name, a one-line description and a starter prompt for this skill.
interface:
display_name: "Codex SDK Automation"
short_description: "Drive Codex from TypeScript with real guardrails"
default_prompt: "$codex-sdk-automation Write a Node script that runs Codex over these 20 repos and collects a JSON report" About this skill
Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.