Codex
Installs the Codex CLI, OpenAI's terminal coding agent, and configures it.
What it does
- Auto-installs and updates the Codex CLI to the latest release with the standalone installer
- Merges
codex_configinto~/.codex/config.toml, leaving the state Codex keeps in that file alone - Optionally links
~/.codex/AGENTS.mdto the shared~/AGENTS.mdfrom the agents role (off by default) - Materializes the selected Agent Skills (
codex_skills, from the agents role's synced sources) into~/.codex/skills - Sets up per-shell multi-account isolation (
codex_accounts, selected withcodex-account) - Installs the
chatgptalias and the account helper functions with completion
Variables
| Variable | Default | Description |
|---|---|---|
codex_version | latest | latest tracks the newest release; an x.y.z pin installs exactly that on every apply. The installer has no stable channel |
codex_config | approval_policy, sandbox_mode, privacy keys, tui.status_line | Settings merged into codex_config_path |
codex_home | ~/.codex | Codex's config directory, the CODEX_HOME default: config, login, skills, and session state |
codex_config_path | ~/.codex/config.toml | Destination for the merged config; write-through if it is a symlink |
codex_agentsmd_path | ~/.codex/AGENTS.md | Where the symlink to the shared ~/AGENTS.md is created |
codex_agentsmd_enabled | false | Link codex_agentsmd_path to the shared ~/AGENTS.md, here and in each account dir; off leaves an existing file untouched |
codex_accounts | [] | Named accounts for per-shell account selection; each gets ~/.codex/accounts/<name> as its CODEX_HOME with the role-managed config deployed into it |
codex_skills | | The skills materialized into the skills path: git source URL mapped to all or a list of skill names; defaults to the whole machine inventory the agents role syncs |
codex_skills_path | ~/.codex/skills | Where the selected skills are copied, Codex's personal skills discovery path |
codex_aliases | chatgpt | Shell aliases rendered to ~/.profile.d/aliases/codex.sh |
codex_functions | codex-account, codex-authenticate | Shell functions rendered alongside the aliases |
codex_completions | account-name completion | Completion functions bound to the aliases and helpers |
Config (defaults)
This is the default application config the role ships for Codex, merged into ~/.codex/config.toml. With hash_behaviour = merge, your codex_config overrides deep-merge onto these defaults, so you change only the keys you set. The full set of keys is documented in the Codex configuration reference:
| Setting | Value | Effect |
|---|---|---|
approval_policy | on-request | Codex asks before it runs something the sandbox would block, the interactive setting upstream recommends (untrusted asks for everything, never for nothing) |
sandbox_mode | workspace-write | Commands may write in the working tree and $TMPDIR, and reach nothing else; network access stays off until you allow it under sandbox_workspace_write |
analytics.enabled | false | Stops the anonymous usage and health data Codex otherwise sends to OpenAI on a timer |
otel.metrics_exporter | none | Keeps OpenTelemetry metrics on the machine; the upstream default is statsig, which exports them. Point it at your own collector if you run one |
tui.status_line | current-dir, git-branch, model-with-reasoning, context-used, five-hour-limit, weekly-limit | The footer shows the directory, branch, model with its reasoning effort, context use, and the 5-hour and weekly limits, close to the status line the claude role deploys |
Rendered, the defaults produce this ~/.codex/config.toml:
approval_policy = "on-request"
sandbox_mode = "workspace-write"
[analytics]
enabled = false
[otel]
metrics_exporter = "none"
[tui]
status_line = ["current-dir", "git-branch", "model-with-reasoning", "context-used", "five-hour-limit", "weekly-limit"]Codex has no custom-command status line: tui.status_line takes only its built-in item IDs, and /statusline in the TUI lists them. Each apply writes the role's list back over whatever the key holds, so change the items through your own codex_config.tui.status_line.
feedback.enabled is deliberately left at its upstream default: it only gates the /feedback command, which sends nothing until you run it. Set it false in your own codex_config to turn that off too.
How the merge works
Codex writes its own state into config.toml. Trusting a directory adds a [projects."/home/you/repo"] table with a trust_level, and other settings land there as you change them in the TUI. A template would wipe all of that on every apply, so instead each apply reads the live file, deep-merges codex_config onto it in the file's own key order, and writes it back only when the merge changed something:
approval_policy = "on-request"
sandbox_mode = "workspace-write"
[analytics]
enabled = false
[otel]
metrics_exporter = "none"
[tui]
status_line = ["current-dir", "git-branch", "model-with-reasoning", "context-used", "five-hour-limit", "weekly-limit"]
[projects."/home/you/projects/dotfiles"]
trust_level = "trusted"Two consequences worth knowing. The first apply on a machine that already ran Codex rewrites the file in normalized form: comments and blank-line grouping are lost, since the config is parsed and re-emitted rather than patched. Everything else keeps its position, so later applies show a diff only when a setting actually changes. And a merge cannot remove a key: to drop something the role once wrote, delete it from config.toml and from your codex_config.
Examples
codex_config deep-merges onto the shipped defaults (with hash_behaviour = merge), so set only the keys you want to change:
codex_version: 0.147.0 # default: latest
codex_config:
approval_policy: never # default: on-request
sandbox_mode: read-only # default: workspace-write
check_for_update_on_startup: false # not shipped by default; the role owns the version
sandbox_workspace_write:
network_access: true # not shipped by default (network off in the sandbox)Pinning codex_version and turning off the startup update check keeps every machine on the version the playbook applies, instead of one that updated itself between applies.
Skills
Agent Skills teach the agent a workflow, a convention, or a runbook; Codex discovers personal skills in ~/.codex/skills/<name>/ and reads the same SKILL.md format as Claude Code, so one inventory feeds both tools. The agents role owns the machine's skill inventory (agents_skills, git sources synced into a local cache), and this role copies the selection into codex_skills_path on every apply, so a skill edit in its source repository lands with the next apply.
codex_skills defaults to the whole inventory, so listing a source in agents_skills is enough to get its skills into Codex. Override it to narrow what this tool receives; every source named here must also be in agents_skills, which is what the cache syncs:
agents_skills: # the machine inventory (agents role)
git@github.com:example/skills.git: all
https://github.com/anthropics/skills:
- skill-creator
codex_skills: # default: the whole agents_skills inventory
git@github.com:example/skills.git:
- blog-postThe role tracks what it deploys in a .dotfilespro manifest next to the skills, so a skill dropped from the selection is removed on the next apply while skills you placed in the directory by hand are left alone. Project skills (<repo>/.codex/skills/) are not touched; this manages only the personal scope.
Accounts
Codex keeps its login in a single config dir (~/.codex), so two ChatGPT accounts on the same machine, say a personal one and a company one, cannot coexist there: a codex login in one shell logs every other shell over. Codex has no account switcher of its own, but it honors CODEX_HOME, which relocates the whole config dir including auth.json. The role builds per-shell account selection on that, the same per-shell context isolation the claude, googlecloud, kubernetes, digitalocean, and gitlab roles apply:
codex_accounts: # default: [] (single account, nothing extra deployed)
- personal
- workEach listed name gets ~/.codex/accounts/<name> as its config dir, and every apply deploys the role-managed config into it: the merged config.toml, a symlink to the shared ~/AGENTS.md (when codex_agentsmd_enabled is on), and a symlink to the managed skills directory, so a fresh account starts with the same setup as ~/.codex. Everything else stays per account: the credential (auth.json), history, and session state.
Log in once per account, then select per shell:
codex-authenticate work # select ~/.codex/accounts/work and run the interactive login
codex-account work # this shell now runs Codex as the work account
codex-account personal # while another shell holds the personal one
codex-account # no argument: show the current account and login statusWith no account selected Codex keeps using ~/.codex, so an existing login keeps working as the implicit default account; the account dirs only apply where you export one.
AGENTS.md
The role leaves ~/.codex/AGENTS.md alone unless you set codex_agentsmd_enabled: true, so an apply never replaces an AGENTS.md you already have there. Codex reads that file as its global instructions and layers a repository's own AGENTS.md on top of it, so enabled, the role only has to point Codex at the shared file the agents role renders:
~/.codex/AGENTS.md -> ~/AGENTS.mdcodex_agentsmd_enabled: true # default: false, which leaves ~/.codex/AGENTS.md untouchedNo import directive is involved, unlike the CLAUDE.md the claude role deploys, and nothing is copied: an agents_agentsmd_sections change reaches Codex the moment the agents role rewrites ~/AGENTS.md. The link is created even before that file exists, so the order of a first apply does not matter. Enabled, the role owns codex_agentsmd_path: a file already sitting there is replaced by the link.
For rules only Codex should read, keep your own AGENTS.md in your dotfiles repo, leave the switch off, and let the dotfiles role link it:
dotfiles_list:
- src: codex/AGENTS.md # ~/.codex/AGENTS.md -> ~/.dotfiles/codex/AGENTS.md
dest: .codex/AGENTS.mdAliases
Rendered to ~/.profile.d/aliases/codex.sh.
| Alias | Command | Description |
|---|---|---|
chatgpt | codex-account | The service name as an entry point to the account helper |
And the account functions, with completion on the account names:
| Function | Description |
|---|---|
codex-account <name> | Select the account for this shell (exports CODEX_HOME); no argument shows the current account and login status |
codex-authenticate <name> | Select the account and run the interactive codex login |
Troubleshooting
The apply reinstalls Codex every time
The role probes codex --version with ~/.local/bin on the PATH, which is where the standalone installer puts the binary. A Codex installed some other way (Homebrew, npm install -g @openai/codex) sits elsewhere, and the installer warns that PATH order then decides which one runs. Remove the other install, or point CODEX_INSTALL_DIR at the location you want and keep it ahead on the PATH.
A shell profile grew a Codex installer PATH block
The installer appends a # >>> Codex installer >>> block to .bashrc (or .zshrc, .profile) when its bin directory is missing from the PATH it runs with. The role hands it the interactive PATH so it takes the "already configured" branch instead, but an installer you ran by hand will have added the block. Delete it; the unix role already puts ~/.local/bin on the PATH.
A self-update landed inside an account directory
~/.local/bin/codex is a symlink into $CODEX_HOME/packages/standalone/, resolved when the installer runs. Running the installer, or letting Codex update itself, from a shell where codex-account has exported CODEX_HOME puts that payload under the account directory. Re-run the apply from a shell with no account selected to put it back under ~/.codex.