Run Codex in a sandbox
Last updated Oct 13, 2026
Run the Codex CLI ↗︎ in a Linux sandbox to edit a GitHub repository from a prompt, then read its changes as a diff. Codex calls OpenAI models through AI Gateway. Your Worker adds the gateway token to those requests, and the sandbox can reach only your gateway and github.com.
Prerequisites
- The
sandbox-coding-agentproject from Build a coding agent runner. This page replaces the image, the model, and the parts of the Durable Object that are specific to the agent. - OpenAI credentials in AI Gateway, either Unified Billing credits or an OpenAI API key stored as a provider key.
Switch the Worker to Codex
- Replace the
Dockerfile. Only the highlighted line differs from theDockerfilein the tutorial:
Dockerfiledockerfile
```dockerfile FROM node:24-trixie-slim
RUN apt-get update \ && apt-get install -y --no-install-recommends bash ca-certificates git ripgrep \ && rm -rf /var/lib/apt/lists/*
RUN npm install --global --ignore-scripts @openai/codex@0.156.1
COPY --from=docker.io/cloudflare/sandbox:1.0.1 /usr/local/bin/sandbox-shim /usr/local/bin/sandbox-shim
WORKDIR /workspace CMD ["sleep", "infinity"] ```
The Codex npm package ships its native binaries as dependencies, so it installs without running install scripts. The image pins the version, because the flags and event format of Codex can change between releases.
2. In wrangler.jsonc, set MODEL to an OpenAI model ID that your gateway can serve:
jsonc
{
"vars": {
"AI_GATEWAY_ACCOUNT_ID": "<ACCOUNT_ID>",
"AI_GATEWAY_ID": "default",
"MODEL": "gpt-6-sol",
},
}
toml
[vars]
AI_GATEWAY_ACCOUNT_ID = "<ACCOUNT_ID>"
AI_GATEWAY_ID = "default"
MODEL = "gpt-6-sol"
Codex sends requests in the OpenAI Responses format, so it works only with OpenAI models that support the Responses API.
3. In src/sandbox.ts, replace the ResultEvent constant with a path for the final message from Codex and a schema for its failure events:
src/sandbox.tsts
``ts
const lastMessagePath =${taskDirectory}/last-message.txt`;
const FailureEvent = z.union([ z.object({ type: z.literal("turn.failed"), error: z.object({ message: z.string() }), }), z.object({ type: z.literal("error"), message: z.string() }), ]); ```
- Replace the
agentCommand()andagentEnv()methods:
src/sandbox.tsts
```ts
export class AgentSandbox extends DurableObject
private agentCommand(prompt: string): string[] {
const baseUrl = `https://gateway.ai.cloudflare.com/v1/${this.env.AI_GATEWAY_ACCOUNT_ID}/${this.env.AI_GATEWAY_ID}/openai`;
return [
"codex",
// Run one task without an interactive terminal
"exec",
// Write one JSON event per line to the events file
"--json",
// Skip the session files that Codex would otherwise save
"--ephemeral",
"--dangerously-bypass-approvals-and-sandbox",
// Write the final message to a file outside the repository
"--output-last-message",
lastMessagePath,
"--model",
this.env.MODEL,
// Add a custom provider that sends requests to the OpenAI endpoint of your gateway
"--config",
'model_provider="cloudflare-ai-gateway"',
"--config",
`model_providers.cloudflare-ai-gateway={ name = "Cloudflare AI Gateway", base_url = ${JSON.stringify(baseUrl)}, wire_api = "responses" }`,
// Stop the analytics and update requests from Codex
"--config",
"analytics.enabled=false",
"--config",
"check_for_update_on_startup=false",
// Stop Codex from syncing its plugin marketplace, which clones a
// repository from `github.com` on every run
"--config",
"features.plugins=false",
"--",
prompt,
];
}
private agentEnv(): Record<string, string> {
return {};
}
} ```
--dangerously-bypass-approvals-and-sandbox turns off the approval prompts and the built-in command sandbox of Codex. The container limits what commands from Codex can reach instead.
A custom provider sends no API key, and the Outbound entrypoint adds the gateway token.
Codex reads the certificate for intercepted HTTPS from SSL_CERT_FILE, which trustEnv in the runner sets.
5. Replace the outcome() method:
src/sandbox.tsts
```ts
export class AgentSandbox extends DurableObject
private async outcome(exitCode: number): Promise<TaskStatus> {
if (exitCode === 0) {
return {
state: "succeeded",
result: (await this.readOptionalText(lastMessagePath)) ?? "",
};
}
let failure: string | undefined;
const events = await this.files.readFile(eventsPath);
for await (const line of readLines(events.body)) {
const parsed = FailureEvent.safeParse(parseJson(line));
if (parsed.success) {
failure =
parsed.data.type === "error"
? parsed.data.message
: parsed.data.error.message;
}
}
if (failure === undefined) {
const stderr = (await this.readOptionalText(stderrPath)) ?? "";
return {
state: "failed",
error: `codex exited with ${exitCode}: ${stderr.slice(-2000)}`,
};
}
return { state: "failed", error: failure };
}
} ```
Codex exits with a nonzero code when its turn fails, so the exit code decides the outcome. A failed task returns the message from the last turn.failed or error event. Codex retries a failing model request five times, and the task fails after about 20 seconds.
6. Deploy your Worker:npmyarnpnpm
npx wrangler deploy
yarn wrangler deploy
pnpm wrangler deploy
The AI_GATEWAY_TOKEN secret from the tutorial stays in place. A running container keeps the image it started with, so use a new sandbox name for Codex.
Run a task
- Clone a repository into a sandbox named
codex-1:
sh
curl "$WORKER_URL/sandboxes/codex-1/repository" \
--json '{"url": "https://github.com/octocat/Hello-World"}'
- Start a task:
sh
curl "$WORKER_URL/sandboxes/codex-1/task" \
--data "Add a file NOTES.md with a one-sentence summary of this repository. Do not commit."
- Check the task until its state is
succeededorfailed:
sh
curl "$WORKER_URL/sandboxes/codex-1/task"
A succeeded task includes the final message that Codex wrote.
4. Read the changes:
sh
curl "$WORKER_URL/sandboxes/codex-1/diff"
The diff shows the new NOTES.md file.
Related resources
- Build a coding agent runner: how the runner starts the sandbox, runs the task, and reads the diff.
- Codex example ↗︎: a separate deployable Worker that runs Codex.
- OpenAI Codex in AI Gateway: route Codex through AI Gateway from your own terminal.
Was this helpful?
YesNo
On this page
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/sandbox/coding-agents/codex/#page","headline":"Run Codex in a sandbox","description":"Run the Codex CLI on a GitHub repository in a Linux sandbox and read its changes as a diff.","url":"https://developers.cloudflare.com/sandbox/coding-agents/codex/","inLanguage":"en","image":"https://developers.cloudflare.com/sandbox/coding-agents/codex/og.png?v=b387b053e2cfc995","dateModified":"2026-10-13","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
description: Deploy Cursor self-hosted machines that run each assigned session in its own Linux sandbox on Containers. title: Run Cursor Cloud Agents in a sandbox image: https://developers.cloudflare.com/sandbox/coding-agents/cursor/og.png?v=d5367f1d93a4980d
Documentation Index
Fetch the complete documentation index at: https://developers.cloudflare.com/sandbox/llms.txt
Use this file to discover all available pages before exploring further.
Docs