Skip to content

Codex CLI onboarding

Editions: OSS, Cloud, Enterprise. Unless stated otherwise, everything on this page ships in OSS.

preloop agents onboard "Codex CLI" enrolls Codex, routes model traffic through the Preloop gateway, and can install approval hooks with --approvals. Codex keeps ~/.codex/config.toml for its own settings. Agent Control does not write that file.

Onboard, verify, roll back

preloop agents discover
preloop agents onboard "Codex CLI"              # MCP firewall + model gateway
preloop agents onboard "Codex CLI" --approvals  # also route tool calls to Preloop policy and approvals

Onboarding backs up ~/.codex/config.toml (~/.codex/config.json is only a legacy fallback) and adds a managed [mcp_servers.preloop] entry that points at the Preloop MCP endpoint. --approvals adds hooks to ~/.codex/hooks.json: PreToolUse evaluates your central tool rules, and PermissionRequest turns the prompts Codex would show you into Preloop approval requests you can answer from mobile, watch, or the web console.

preloop agents status "Codex CLI"
preloop agents validate "Codex CLI"
preloop agents restore "Codex CLI"
preloop agents offboard "Codex CLI"

See the CLI quick start, Safety Layer and access rules and Claude Code for the equivalent flow on other agents.

Agent Control sidecar

Codex has no in-process plugin API for operator messages, so Agent Control runs as a sidecar:

  • package @preloop-ai/codex-plugin
  • command preloop-codex-plugin
  • source runtime-plugins/codex-preloop
  • config ~/.codex/preloop-control.json

Onboarding installs the package with npm when it is published, or from the local source directory when that checkout is present. It writes the same control keys the Claude sidecar uses (enabled, protocol, runtime, control_ws_url, bearer_token, and the runtime identity fields). Nothing Codex-specific is added to that file.

preloop agents onboard "Codex CLI"
preloop agents validate "Codex CLI"
preloop codex sidecar enable
preloop codex sidecar status
preloop codex sidecar disable

validate reports control_config_written, control_plugin_installed, control_plugin_verified, and control_channel_configured separately. preloop codex sidecar run execs preloop-codex-plugin run against the control file. It is the command launchd and systemd start.

Offboard removes ~/.codex/preloop-control.json and the sidecar service. ~/.codex/config.toml is restored from the onboarding backup and is not rewritten by Agent Control.

Codex refreshes its ChatGPT login on its own, even when model traffic goes through Preloop, and the Preloop gateway refreshes the copy it stores. That login uses a single-use refresh token, so two holders of one grant revoke each other when either refreshes with a stale token. The Codex permission hook keeps both copies on the same lineage. It pushes the local bundle (~/.codex/auth.json, or the macOS Keychain entry when Codex keeps its login there) when it is newer than the stamp in the local enrollment state. About every two minutes it also reads Preloop's rotation marker, which carries no tokens, and when Preloop's copy is newer it writes that bundle back into the same place Codex reads it. When both copies changed since the last sync, the one with the later last_refresh wins and replaces the other. A pull only happens when the local login and Preloop's copy name the same ChatGPT account. A failed push or pull is logged once, leaves the local login and the stamp as they were, and does not change the permission decision. A host with no local login never gets one written back. When the hook is not installed, run preloop agents sync-credentials "Codex CLI"; it reconciles in both directions and prints which one ran.

For headless hosts, a single holder is still the recommendation: import the login into Preloop, delete the local auth.json, and keep requires_openai_auth = false (the default) on the Preloop model provider in ~/.codex/config.toml, so only Preloop refreshes the grant.

If the provider has already revoked the grant, synchronization cannot repair it. Run preloop agents reconnect "Codex CLI" to sign in and replace only the stored subscription credential, preserving the enrollment and configuration. After a separate codex login, use preloop agents reconnect "Codex CLI" --from-local. Update the CLI if this command is not in your installed release. Recovery uploads the bundle to one credential and attaches legacy split model rows to that owner; it never creates multiple stored copies of the rotating token. The server rejects recently consumed refresh tokens and stops retrying provider-declared invalid grants until fresh credentials are supplied. Transient failures remain retryable. Independent hosts and instances should obtain separate authorization grants; the hook cannot make concurrent refreshes by independent holders atomic.

To push a Codex login through the API yourself, send PUT /api/v1/ai-models/{id} with credential_type: "oauth_openai_codex" and a credential_payload in Preloop's shape, not the key names from auth.json:

{
  "access": "<access token>",
  "refresh": "<refresh token>",
  "account_id": "<ChatGPT account id>",
  "expires": 1893456000000
}

access, refresh, and account_id must be non-empty strings. expires is the access-token expiry as an integer in epoch milliseconds. The server checks the payload when you write it and answers 422 with the missing or invalid keys, without storing anything. access_token, refresh_token, and expires_at are rejected with a hint that names the expected key, and an expires in epoch seconds or microseconds is rejected too. This is the same shape POST /api/v1/ai-models/{id}/credentials/export returns.