Guide
Codex CLI with an API key, without a key in plain text
Codex CLI signs in with ChatGPT or with an OpenAI Platform API key. With a key, Codex can keep the login as plain text in ~/.codex/auth.json (setting file), and a key exported in your shell reaches every command Codex runs. Here is how to keep it in the macOS keychain instead.
Checked against OpenAI's Codex documentation on 13 November 2026.
How Codex uses a key
- Interactive
codex: uses the saved login.OPENAI_API_KEYin your shell does not sign you in by itself; you log in once withprintenv OPENAI_API_KEY | codex login --with-api-key, which reads the key from stdin. - Where the login is stored:
cli_auth_credentials_storein~/.codex/config.toml:file(plain textauth.json),keyring(the OS credential store; fails if it isn't available),auto(the credential store when available, otherwiseauth.json) orephemeral(memory of the running process only). codex exec,codex reviewand the SDK also acceptCODEX_API_KEY. OpenAI's advice: set it only for the Codex command that needs it, never for a whole job or shell.
Step 1: keychain instead of auth.json
# ~/.codex/config.toml cli_auth_credentials_store = "keyring"
If an auth.json with a key already exists, run codex logout first, then log in again as below.
Step 2: log in once, straight from Secretariat
Keep the key in Secretariat (capture it from the OpenAI dashboard with ⌃⌥⌘V) and bind it to a profile:
secretariat profile create codex-api --dir '*' secretariat profile bind codex-api OPENAI_API_KEY \ personal/openai/api/api_key secretariat run codex-api -- sh -c \ 'printenv OPENAI_API_KEY | codex login --with-api-key'
Run this in your own terminal, not through an agent. The key goes from your iCloud keychain through a pipe into Codex's keychain entry. The full key doesn't appear in your terminal, your shell history or a file; Codex prints only a shortened form (first and last characters). codex login status confirms the login.
Step 3: scripts and codex exec
For automation, use a second profile that holds only CODEX_API_KEY, so the run gets exactly the one variable it needs:
secretariat profile create codex-exec --dir '*' secretariat profile bind codex-exec CODEX_API_KEY \ personal/openai/api/api_key secretariat run codex-exec -- codex exec "triage open bug reports"
When an agent or a pipe reads the output, Secretariat replaces the key with [masked]. Codex passes its environment on to the commands it runs, keys included: ignore_default_excludes is true by default. Turn the automatic filter for names containing KEY, SECRET or TOKEN on:
# ~/.codex/config.toml [shell_environment_policy] ignore_default_excludes = false
Why keep the key in Secretariat at all
Codex needs it once; you need it again when you rotate it, set up a new Mac, or give the same key to a deploy script, a GitHub secret or a Cloudflare Worker. Secretariat keeps the one copy, syncs it to your Macs, pushes it where it's needed and lists stale copies after a rotation.
What this doesn't prevent
- Codex must hold the key to call the API; a process running as you can reach the keychain entry Codex created.
- Remove old copies:
export OPENAI_API_KEYlines in shell profiles,.envfiles, and a leftover~/.codex/auth.json.
Sources: OpenAI Codex docs on authentication, non-interactive mode and environment variables.