> ## Documentation Index
> Fetch the complete documentation index at: https://docs.novita.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Codex Sandbox: Run OpenAI Codex with Novita

> Learn how to set up a Codex sandbox with Novita AI. Run OpenAI Codex in an isolated environment, work on a cloned repo, use Novita models, and resume sessions.

This guide shows you how to set up a Codex sandbox using Novita Sandbox. You'll configure the environment, launch Codex, and run your coding agent in an isolated runtime.

## What you'll build

You will start a sandbox from the `codex` template — which ships with OpenAI's Codex CLI pre-installed — and hand Codex a coding task. Codex reads and edits code, runs commands, and executes what it writes inside the sandbox, so none of its actions touch your machine. The model can be OpenAI's or any Novita-hosted model.

```mermaid theme={"system"}
flowchart TB
  App["Your app<br/>(novita-sandbox SDK)"] -- "create sandbox,<br/>codex exec" --> Sandbox
  subgraph Sandbox["Novita Sandbox · codex template"]
    Codex["Codex CLI"] --> Tools["Files · Shell · Code"]
  end
  Codex -- "Responses API" --> Model["OpenAI API<br/>or Novita LLM API"]
```

## Prerequisites

* **Novita API key** — create one in [Key Management](https://novita.ai/settings/key-management) and export it as `NOVITA_API_KEY`.
* **A model key** — an OpenAI key exported as `CODEX_API_KEY` for the Quickstart, or nothing extra if you [use Novita models](#use-novita-models).
* **Novita Sandbox SDK** — Python 3.10+ or Node.js 20+:

```bash Python icon="python" theme={"system"}
pip install novita-sandbox
```

```bash JavaScript & TypeScript icon="js" theme={"system"}
npm install novita-sandbox
```

* **Codex version** — the `codex` template comes with the Codex CLI pre-installed; nothing to install. Run `codex --version` in a sandbox to see the bundled version.

## Quickstart

Create a sandbox from the `codex` template, run one non-interactive task with `codex exec`, stream its output, and kill the sandbox.

```python Python icon="python" theme={"system"}
import os
from novita_sandbox import Novita

sandbox = Novita().sandbox.create(
    "codex", timeout=1800, envs={"CODEX_API_KEY": os.environ["CODEX_API_KEY"]}
)
try:
    sandbox.commands.run(
        'codex exec --full-auto --skip-git-repo-check "Write fizzbuzz.py, run it, and show the output"',
        cwd="/home/user",
        on_stdout=lambda d: print(d, end=""),
        on_stderr=lambda d: print(d, end=""),
        timeout=0,
    )
finally:
    sandbox.kill()
```

```js JavaScript & TypeScript icon="js" theme={"system"}
import { Novita } from 'novita-sandbox'

const sandbox = await new Novita().sandbox.create('codex', {
  timeoutMs: 1_800_000,
  envs: { CODEX_API_KEY: process.env.CODEX_API_KEY },
})
try {
  await sandbox.commands.run(
    'codex exec --full-auto --skip-git-repo-check "Write fizzbuzz.py, run it, and show the output"',
    {
      cwd: '/home/user',
      onStdout: (d) => process.stdout.write(d),
      onStderr: (d) => process.stderr.write(d),
      timeoutMs: 0,
    }
  )
} finally {
  await sandbox.kill()
}
```

> [!NOTE]
>
> **`codex exec "<prompt>"`** runs Codex non-interactively: it processes the prompt, prints the result, and exits, which makes it scriptable. **`--full-auto`** approves the file and command actions Codex would otherwise prompt for — use it only in an isolated sandbox like this one. **`--skip-git-repo-check`** lets Codex run outside a Git repository.

## How it works

Codex gets a full Linux environment inside the sandbox. You drive it with the SDK: `files` and `git` to move code in and out, `commands` to run Codex and anything else. The snippets below assume a `sandbox` created as in the Quickstart.

### Execute generated code

Codex writes code and runs it in the sandbox's shell. Its output streams back through `on_stdout` / `on_stderr`, and the call returns the exit code.

```python Python icon="python" theme={"system"}
result = sandbox.commands.run(
    'codex exec --full-auto --skip-git-repo-check "Create a Flask app in app.py with a /health route and test it"',
    cwd="/home/user",
    on_stdout=lambda d: print(d, end=""),
    timeout=0,
)
print("exit code:", result.exit_code)
```

```js JavaScript & TypeScript icon="js" theme={"system"}
const result = await sandbox.commands.run(
  'codex exec --full-auto --skip-git-repo-check "Create a Flask app in app.py with a /health route and test it"',
  { cwd: '/home/user', onStdout: (d) => process.stdout.write(d), timeoutMs: 0 }
)
console.log('exit code:', result.exitCode)
```

### Read and write files on a cloned repository

The most common workflow: clone a repository with [`sandbox.git.clone`](https://docs.novita.ai/guides/sandbox-git-integration) (with credentials for private repos), let Codex change it, then read the result back — here, the diff.

```python Python icon="python" theme={"system"}
sandbox.git.clone(
    "https://github.com/your-org/your-repo.git",
    path="/home/user/repo",
    username="x-access-token",
    password=os.environ["GITHUB_TOKEN"],
    depth=1,
)
sandbox.commands.run(
    'codex exec --full-auto "Add error handling to all API endpoints"',
    cwd="/home/user/repo",
    timeout=0,
)
print(sandbox.commands.run("git diff", cwd="/home/user/repo").stdout)
```

```js JavaScript & TypeScript icon="js" theme={"system"}
await sandbox.git.clone('https://github.com/your-org/your-repo.git', {
  path: '/home/user/repo',
  username: 'x-access-token',
  password: process.env.GITHUB_TOKEN,
  depth: 1,
})
await sandbox.commands.run('codex exec --full-auto "Add error handling to all API endpoints"', {
  cwd: '/home/user/repo',
  timeoutMs: 0,
})
console.log((await sandbox.commands.run('git diff', { cwd: '/home/user/repo' })).stdout)
```

To move individual files instead, use `sandbox.files.write` and `sandbox.files.read`; see [Read and write files](https://docs.novita.ai/guides/sandbox-filesystem-read-write.md).

### Run commands yourself

Run any command next to the agent — install dependencies before it starts, or verify its work afterwards without trusting its own report.

```python Python icon="python" theme={"system"}
sandbox.commands.run("pip install -r requirements.txt", cwd="/home/user/repo", timeout=300)
sandbox.commands.run('codex exec --full-auto "Fix the failing tests"', cwd="/home/user/repo", timeout=0)

check = sandbox.commands.run("python3 -m pytest -q", cwd="/home/user/repo")
print(check.stdout)
```

```js JavaScript & TypeScript icon="js" theme={"system"}
await sandbox.commands.run('pip install -r requirements.txt', { cwd: '/home/user/repo', timeoutMs: 300_000 })
await sandbox.commands.run('codex exec --full-auto "Fix the failing tests"', { cwd: '/home/user/repo', timeoutMs: 0 })

const check = await sandbox.commands.run('python3 -m pytest -q', { cwd: '/home/user/repo' })
console.log(check.stdout)
```

> [!TIP]
>
> A command that exits non-zero raises `CommandExitException` (Python) or `CommandExitError` (JavaScript). Catch it when a failing test run is an expected outcome.

### Resume a session

Codex can continue a previous session, so you can run a multi-step workflow across several calls. Start with `--json` to capture the `thread_id` from the first event (`thread.started`), then pass it to `codex exec resume <thread_id>`.

```python Python icon="python" theme={"system"}
import json

initial = sandbox.commands.run(
    'codex exec --full-auto --skip-git-repo-check --json "Create plan.md with a 3-step plan for a TODO CLI app"',
    cwd="/home/user",
    timeout=0,
)
thread_id = json.loads(initial.stdout.strip().splitlines()[0])["thread_id"]

sandbox.commands.run(
    f'codex exec resume {thread_id} --full-auto --skip-git-repo-check "Now implement step 1 of the plan"',
    cwd="/home/user",
    on_stdout=lambda d: print(d, end=""),
    timeout=0,
)
```

```js JavaScript & TypeScript icon="js" theme={"system"}
const initial = await sandbox.commands.run(
  'codex exec --full-auto --skip-git-repo-check --json "Create plan.md with a 3-step plan for a TODO CLI app"',
  { cwd: '/home/user', timeoutMs: 0 }
)
const threadId = JSON.parse(initial.stdout.trim().split('\n')[0]).thread_id

await sandbox.commands.run(
  `codex exec resume ${threadId} --full-auto --skip-git-repo-check "Now implement step 1 of the plan"`,
  { cwd: '/home/user', onStdout: (d) => process.stdout.write(d), timeoutMs: 0 }
)
```

## Use Novita models

To run Codex on a Novita-hosted model instead of OpenAI, write a provider config to `~/.codex/config.toml` before running `codex`, and pass your Novita key instead of `CODEX_API_KEY`. Change `model` to switch to any model in the [Novita model library](https://novita.ai/models). See [Codex with Novita](https://docs.novita.ai/guides/codex.md) for details.

```python Python icon="python" theme={"system"}
sandbox = Novita().sandbox.create(
    "codex", timeout=1800, envs={"NOVITA_API_KEY": os.environ["NOVITA_API_KEY"]}
)
sandbox.files.write("/home/user/.codex/config.toml", """model_provider = "novita"
model = "deepseek/deepseek-v4.1-flash"

[model_providers.novita]
name = "Novita"
wire_api = "responses"
requires_openai_auth = true
base_url = "https://api.novita.ai/openai/v1"
env_key = "NOVITA_API_KEY"
""")
```

```js JavaScript & TypeScript icon="js" theme={"system"}
const sandbox = await new Novita().sandbox.create('codex', {
  timeoutMs: 1_800_000,
  envs: { NOVITA_API_KEY: process.env.NOVITA_API_KEY },
})
await sandbox.files.write('/home/user/.codex/config.toml', `model_provider = "novita"
model = "deepseek/deepseek-v4.1-flash"

[model_providers.novita]
name = "Novita"
wire_api = "responses"
requires_openai_auth = true
base_url = "https://api.novita.ai/openai/v1"
env_key = "NOVITA_API_KEY"
`)
```

> [!NOTE]
>
> Codex supports the Responses API only, so keep `wire_api = "responses"`. Model usage is billed separately from sandbox runtime, at the model's own per-token rate.

## Production tips

* **Timeouts** — `timeout` (seconds; `timeoutMs` in JavaScript) on `sandbox.create` is the sandbox's maximum lifetime; extend it while Codex works with [`set_timeout`](https://docs.novita.ai/guides/sandbox-timeout-handle). Pass `timeout=0` to `commands.run` so a long Codex turn isn't cut off at the SDK's default command timeout. Without long-running mode the timeout is capped by your tier (1, 3, or 4 hours); for longer runs, see [Long-running sandboxes](https://docs.novita.ai/guides/sandbox-long-running.md).
* **Pause and resume** — between steps of a multi-step job, [pause](https://docs.novita.ai/guides/sandbox-pause-resume.md) the sandbox instead of killing it. The repo, installed packages and Codex sessions survive, so `codex exec resume` still works after you reconnect, and CPU and RAM billing stops while it is paused.
* **Concurrency** — each sandbox is isolated, so you can run one Codex task per sandbox in parallel — for example, one per repository or per issue. Concurrent sandboxes are capped by your account tier (5 on Free, 100 on Paid); see [Quota Limits](https://docs.novita.ai/guides/sandbox-quota-limit.md).
* **Cost** — sandboxes are billed per second while running. A 2 vCPU / 2 GiB sandbox costs about \$0.0000260/s, so a 10-minute task is roughly **\$0.016** in sandbox time, plus model tokens. See [Pricing](https://docs.novita.ai/guides/sandbox-pricing.md).
* **Keep secrets out of the sandbox** — pass header-based API keys, such as your model key, as [Secrets](https://docs.novita.ai/guides/sandbox-secret-sandbox-use.md) so code in the sandbox only sees a placeholder. For Git, use a short-lived token scoped to the one repository.

## Troubleshooting

> [accordion] 402: insufficient balance when creating a sandbox
>
> Your account balance can't cover a new sandbox. Top up on the [Billing](https://novita.ai/billing) page or claim the new-user credits described in [Pricing](https://docs.novita.ai/guides/sandbox-pricing.md), then retry.

> [accordion] Codex refuses to run: not inside a trusted Git repository
>
> Codex expects to run inside a Git repository. Add `--skip-git-repo-check` when running in a plain directory, or set `cwd` to a cloned repository.

> [accordion] 401 Unauthorized or model not found from the model API
>
> The key Codex is using doesn't match the provider. With OpenAI, pass `CODEX_API_KEY` in `envs`. With Novita, pass `NOVITA_API_KEY`, keep `env_key = "NOVITA_API_KEY"` in `config.toml`, and make sure `model` matches the Novita model ID exactly.

## Next steps

> [card] [Sandbox SDK reference](https://docs.novita.ai/guides/sandbox-create.md)
>
> Every option for creating and managing sandboxes.

> [card] [Claude Code Sandbox](https://docs.novita.ai/guides/sandbox-claude-code.md)
>
> Run Anthropic's coding agent the same way.

> [card] [Sandbox cookbook](https://github.com/novitalabs/sandbox-cookbook)
>
> Runnable Novita Sandbox examples on GitHub.

> [card] [OpenCode and OpenClaw](https://docs.novita.ai/guides/sandbox-opencode.md)
>
> Run open-source agents in a sandbox.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.