Running Codex
This guide covers running OpenAI Codex CLI in a Moat container.
Prerequisites
- Moat installed
- A ChatGPT plan with Codex access, or an OpenAI API key
- Codex CLI 0.146.x or newer on the host for the one-time subscription login (verified through 0.160.x)
Granting a Codex subscription
Run moat grant codex. Moat launches a fresh Codex login in a private,
temporary CODEX_HOME; it never reads or changes your normal
~/.codex/auth.json.
moat grant codex
The login uses Codex’s device-code flow: it prints a link and a one-time code,
and you sign in from a browser anywhere — the same command works on your laptop,
over SSH, and on a headless host. Do not run codex login yourself to complete
it; that writes to your ordinary CODEX_HOME instead of Moat’s temporary one.
Device-code login is in beta and a workspace administrator can disable it. If
it is unavailable for your account, moat grant openai with an API key is the
supported alternative.
Moat stores its separately issued refresh credential encrypted as
codex-subscription-v1. The temporary plaintext login directory is deleted.
For API-key billing or general OpenAI SDK use, grant the independent openai
credential instead:
export OPENAI_API_KEY="sk-..."
moat grant openai
If both grants are present, Codex uses the ChatGPT subscription. The API-key
placeholder is exported to shell commands via BASH_ENV rather than into the
container environment, so scripts and SDKs can reach it while Codex itself does
not see it and stays on the subscription. This mirrors how a claude plus
anthropic pair is handled.
With neither grant, no auth file is staged at all and Codex prompts for login as it normally would.
How credentials are injected
The container gets a wholly synthetic ~/.codex/auth.json. Moat’s daemon loads
the real encrypted credential and atomically replaces the synthetic bearer and
account headers only for TLS requests to https://chatgpt.com/backend-api/codex/**
and /backend-api/wham/accounts/check on the same origin. Codex 0.160 and newer
use the latter endpoint during startup to discover workspace routing. Moat maps
the selected account ID in that response back to the synthetic identity, while
preserving the server’s backend origin and routing constraints.
The real access token, refresh token, and account ID are never mounted or sent
in the container environment. Subscription auth currently supports only the
default ChatGPT origin and Codex CLI 0.146.x or newer. Moat has verified 0.146.x
through 0.160.x; a newer 0.x release is allowed with a warning, because Codex
ships often and an incompatible release fails visibly (Codex reports it is not
logged in) rather than exposing the credential. A 1.x release is rejected until
it has been verified.
OpenAI API keys remain scoped to api.openai.com.
Generating moat.yaml
Use moat init to auto-generate a moat.yaml for your project:
moat init ./my-project
This scans the project, detects its dependencies and tools, and generates a configuration file using AI. Requires at least one credential granted (e.g., moat grant codex).
Running Codex
Interactive mode
Start Codex in the current directory:
moat codex
Start in a specific project:
moat codex ./my-project
Codex launches in interactive TUI mode with full access to the mounted workspace.
Non-interactive mode
Run with a prompt:
moat codex -p "explain this codebase"
moat codex -p "fix the failing tests"
moat codex -p "add input validation to the user registration form"
Codex runs the prompt with codex exec and exits when complete.
Permission handling
Codex has its own approval prompts and its own sandbox. Inside a moat container both are turned off by default, via the generated ~/.codex/config.toml:
approval_policy = "never"
sandbox_mode = "danger-full-access"
Two reasons. The container is already the isolation boundary, so a second sandbox inside it adds prompts without adding protection. And Codex’s sandbox blocks network access for the commands it runs, which breaks anything routed through the moat proxy — npm install, pip, git push, gh.
Security properties:
The container runs as a non-root user with filesystem access limited to the mounted workspace. Credentials are injected at the network layer and never appear in the container environment. See Security model for the full threat model.
Restoring manual approval:
Use --noyolo to run with Codex’s own defaults instead (approval_policy = "on-request", sandbox_mode = "workspace-write"):
moat codex -p "refactor the API layer" --noyolo
Codex then prompts for confirmation before each potentially destructive operation, and confines the commands it runs to the workspace with the network disabled.
The older --full-auto flag is deprecated and hidden. --full-auto=false still maps to --noyolo; the Codex CLI itself removed the flag from interactive mode and deprecated it on codex exec.
Named runs
Give your run a name for reference:
moat codex --name feature-auth ./my-project
The name appears in moat list and makes it easier to manage multiple runs.
Non-interactive runs
Run Codex non-interactively with a prompt:
moat codex -p "fix the failing tests" ./my-project
Monitor progress:
$ moat list
NAME RUN ID STATE AGE
feature-auth run_a1b2c3d4e5f6 running 5m
$ moat logs -f run_a1b2c3d4e5f6
Adding GitHub access
Grant GitHub access so Codex can interact with repositories:
moat codex --grant github ./my-project
This injects GitHub credentials alongside OpenAI credentials. Codex can:
- Clone repositories
- Push commits
- Create pull requests
- Access private repositories
Configure in moat.yaml for repeated use:
name: my-codex-project
grants:
- codex
- github
Then:
moat codex ./my-project
Adding SSH access
For SSH-based git operations:
moat grant ssh --host github.com
moat codex --grant ssh:github.com ./my-project
Codex can use git@github.com:... URLs for cloning and pushing.
Allowing additional hosts
By default, Codex has network access to OpenAI endpoints (api.openai.com, chatgpt.com, etc.). To allow access to additional hosts:
moat codex --allow-host example.com ./my-project
Or configure in moat.yaml:
network:
rules:
- example.com
- "*.internal.corp"
Session transcripts
When the codex or openai grant is configured, Codex session transcripts written inside the container appear on the host at:
~/.moat/codex/sessions/<workspace>-<id>/YYYY/MM/DD/rollout-<timestamp>-<uuid>.jsonl
The directory name renders the workspace path for readability and appends a short digest of it, so two projects whose paths differ only in punctuation (my-project and my_project) get separate directories rather than sharing one.
The directory is per-workspace and separate from your own ~/.codex/sessions, so a container working on one project cannot read transcripts from another. That matters because transcripts contain whatever the agent saw — source, data, credentials in error messages — and one project’s data should not reach another project’s agent.
Opt out of syncing entirely with sync_logs: false.
To use your own Codex history instead, so container sessions show up alongside host ones:
codex:
shared_sessions: true
This is all-or-nothing. Codex partitions sessions by date rather than by workspace and cannot be told to write elsewhere, so sharing exposes your transcripts from every project to the container. Prefer the default unless you specifically need one combined history.
The SQLite databases in ~/.codex are never shared in either mode — concurrent runs writing one shared SQLite file risks corrupting it. A consequence is that codex resume on the host may not list sessions created inside a container, since resume reads that index. The transcripts themselves are complete.
MCP servers
Both kinds of MCP server work with Codex, and both are written to the [mcp_servers] table of the generated ~/.codex/config.toml — Codex reads MCP servers from config.toml only, so nothing is written to .mcp.json.
Remote servers declared at the top level of moat.yaml become streamable HTTP entries pointing at the proxy relay, which injects the real credential:
mcp:
- name: context7
url: https://mcp.context7.com/mcp
Sandbox-local servers declared under codex.mcp: become stdio entries:
codex:
mcp:
filesystem:
command: npx
args: ["-y", "@modelcontextprotocol/server-filesystem", "/workspace"]
cwd: /workspace
See MCP servers for the full configuration reference.
Workspace snapshots
Moat captures workspace snapshots for recovery and rollback. See Snapshots for configuration and usage.
Example: Code review workflow
-
Grant credentials:
moat grant openai moat grant github -
Create
moat.yaml:name: code-review grants: - openai - github snapshots: triggers: disable_pre_run: false -
Run Codex with a review prompt:
moat codex -p "Review the changes in the last 3 commits. Focus on security issues and suggest improvements." -
View what Codex did:
moat logs moat trace --network
Troubleshooting
”No OpenAI credentials found”
Create an API key from platform.openai.com/api-keys and grant it:
export OPENAI_API_KEY="sk-..."
moat grant openai
Codex hangs on startup
Check that you’re not running in a directory without a moat.yaml that specifies a conflicting configuration. Try:
moat codex --name test ~/empty-dir
Network errors
Verify the OpenAI credential is granted:
moat run --grant openai -- curl -s https://api.openai.com/v1/models -H "Authorization: Bearer test"
Related guides
- SSH access — Set up SSH for git operations
- Snapshots — Protect your workspace with snapshots
- Exposing ports — Access services running inside containers