Codex MCP Profiles
Codex-specific MCP profiles
Codex Per-Profile Runtime Homes
Prompt-first procedure: Describe the outcome you want in your agent conversation. The agent should select and load the appropriate AIWG assets, explain material changes, request any needed approval, and report verification evidence. Exact commands and flags appear only in the CLI reference.
Codex has no native per-session config flag (unlike `claude the mcp-config option`). AIWG implements OAuth isolation per MCP profile by giving each profile its own runtime home directory. Codex is launched with `HOME=<runtime-home>` so all config reads, auth token writes, and OAuth flows are scoped to that profile.
This mirrors the sysops `codex-role.sh` pattern from `roctinam/sysops`.
How It Works
~/.codex/ ← global Codex home (shared)
├── history.jsonl ← symlinked into each runtime home
├── sessions/ ← symlinked into each runtime home
└── roles-runtime/ ← runtime homes live here
├── dev/ ← runtime home for "dev" profile
│ ├── config.toml ← profile-scoped MCP server config
│ ├── auth.json ← OAuth tokens (isolated per profile)
│ ├── history.jsonl → ← symlink to ~/.codex/history.jsonl
│ └── sessions/ → ← symlink to ~/.codex/sessions/
└── ops/ ← runtime home for "ops" profile
├── config.toml
├── auth.json
├── history.jsonl →
└── sessions/ →
Isolated per profile: `auth.json`, `.credentials`, `config.toml` Shared across profiles: `history.jsonl`, `sessions/`
Session Flow
sequenceDiagram
participant User
participant the agent-owned participant operation FS as Filesystem
participant Codex
User->>aiwg: the agent-owned session operation the provider option codex the profile option ops
aiwg->>FS: mkdir ~/.codex/roles-runtime/ops/
aiwg->>FS: symlink history.jsonl (if source exists)
aiwg->>FS: symlink sessions/ (if source exists)
aiwg->>FS: read ~/.codex/config.toml (strip [mcp_servers.*] blocks)
aiwg->>FS: resolve "ops" profile servers from ~/.aiwg/mcp-profiles.json
aiwg->>FS: write ~/.codex/roles-runtime/ops/config.toml
Note over FS: Profile-scoped config with only ops servers
aiwg->>Codex: spawnSync('codex', [], { env: { HOME: '~/.codex/roles-runtime/ops' } })
Note over Codex: Reads config from runtime home, not ~/.codex
alt First run (no auth.json in runtime home)
Codex->>User: OAuth login prompt
User->>Codex: authenticate
Codex->>FS: write auth.json to runtime home (isolated)
else Already authenticated
Codex->>FS: read auth.json from runtime home
end
Codex->>User: session active with ops profile servers
Using Profile Sessions
Use AIWG to complete this documented outcome: Using Profile Sessions
Have it inspect the current state, explain the plan, ask before material
changes, and report the result with verification evidence.
Each profile authenticates independently. Logging out of one profile does not affect other profiles.
Explicit Login
Run OAuth login for a profile without starting a full session:
Use AIWG to complete this documented outcome: Run OAuth login for a profile without starting a full session
Have it inspect the current state, explain the plan, ask before material
changes, and report the result with verification evidence.
This calls `codex login` with `HOME`/`CODEX_HOME` set to the profile runtime home. Useful for pre-authenticating profiles before starting a session. With a terminal attached, this runs the normal interactive OAuth flow — the same one a first the agent-owned session operation would trigger.
Running headless (#3062): without a terminal, pass exactly one of three flags naming where the API key comes from — see Supplying credentials safely for which one to prefer and why:
Use AIWG to complete this documented outcome: Running headless (#3062): without a terminal, pass exactly one of three flags naming where the API key comes from — see Supplying credentials safely for which one to prefer and why
Have it inspect the current state, explain the plan, ask before material
changes, and report the result with verification evidence.
In every case the key reaches `codex login the with-api-key option` over stdin only — it never appears on argv, in logs, or in any error message. the api-key-stdin option refuses if stdin is a terminal (run the plain interactive login instead) or empty. `the api-key-file option <path>` refuses unless the file is a regular file, not a symlink, with exactly one hard link, owned by you, and not readable or writable by group or other (`chmod 600`) — never printing the path's contents. the api-key-env option reads the named variable and, as of the go/no-go review's blocker 1, is also kept out of this process's own child environment and out of `NODE_DEBUG=child_process` diagnostics, including a background subprocess this CLI itself might otherwise start (see Supplying credentials safely); it remains the least preferred of the three because the variable is still visible to every other child process the invoking shell starts. Only one of the three may be given — more than one is a usage error naming the flags given. If the named variable (or file) is unset/missing, the error names it (never a value, since there was none). Without a terminal and without any of the three, the command errors naming all three flags instead of hanging waiting for input nobody can supply.
This command requires the built CLI (`npm run build:cli` in a source checkout, or an installed `aiwg`) — if the compiled module is missing it reports that directly instead of attempting to load TypeScript source and failing deep inside an unrelated import (#3062 second review, finding 3).
Running A Profile Session Headless
Without a terminal, launching a codex session with a profile and no the prompt option/the prompt-file option refuses instead of hanging waiting for a terminal (same rule as the plain, non-profile launch). Pass the prompt option/the prompt-file option to run the profile's runtime home headless instead — the prompt travels on stdin via `codex exec -` (see the headless-running reference and `docs/providers/codex-direct.md` for the evidence), never on argv.
Supplying Credentials Safely
Go/no-go review, blocker 1. When a headless command needs a secret (an API key, a token), prefer these methods in this order — most preferred first — because each one is visible to a progressively wider audience on the machine running it:
1. Interactive login at a terminal. Nothing is ever supplied on the command line or in the environment at all; `codex` prompts you directly.
Use AIWG to complete this documented outcome: 1. Interactive login at a terminal. Nothing is ever supplied on the command line or in the environment at all; codex prompts you directly.
Have it inspect the current state, explain the plan, ask before material
changes, and report the result with verification evidence.
2. Piped from a secret manager, over this process's own stdin. The secret exists only for the lifetime of the pipe; it is never written to an environment variable or a file, and never appears on any command line.
vault read -field=key secret/openai | the agent-owned mcp operation profile login ops the api-key-stdin option
3. A private file. The secret sits on disk, but only you (and anything running as you) can read it — enforced, not just documented: a mode-600, non-symlink, single-hard-link regular file you own.
Use AIWG to complete this documented outcome: 3. A private file. The secret sits on disk, but only you (and anything running as you) can read it — enforced, not just documented: a mode-600, non-symlink, single-hard-link regular file you own.
Have it inspect the current state, explain the plan, ask before material
changes, and report the result with verification evidence.
4. A named environment variable. The least preferred of the four: an environment variable is visible to every other child process the invoking shell starts for the lifetime of that shell, not just to the one command you meant it for.
Use AIWG to complete this documented outcome: 4. A named environment variable. The least preferred of the four: an environment variable is visible to every other child process the invoking shell starts for the lifetime of that shell, not just to the one command you meant it for.
Have it inspect the current state, explain the plan, ask before material
changes, and report the result with verification evidence.
Two things make option 4 riskier than it looks, and apply to any secret-bearing environment variable, not just this command:
- A command-line argument is visible in a process listing (`ps`, `/proc`,
task managers) to anyone who can see your process list — never pass a secret value itself as an argument; a variable name is fine.
- Debug logging of child processes prints environments. Setting
`NODE_DEBUG=child_process` (or an equivalent diagnostic in another language's child-process library) makes that process print every child's full spawn options — including its environment — to your own terminal. This is exactly how the go/no-go review found blocker 1: a background subprocess this CLI itself starts on every invocation (the update-notifier bootstrap, `src/update/notifier.mjs`) used to inherit the whole parent environment, which under `NODE_DEBUG=child_process` printed whatever the api-key-env option named. Fixed two ways, both durable regardless of which command runs: `bin/aiwg.mjs` now never starts that bootstrap subprocess at all for a command it recognizes as credential-handling (today: the agent-owned mcp operation, the agent-owned decision operation), and the bootstrap subprocess's own environment is now an explicit allow-list (`notifierChildEnv()`) instead of a full copy of the parent's, so a secret this CLI does not yet know to name is still never copied into it, for any command.
Shared State Policy
The `SharedStatePolicy` controls which files are symlinked (shared) vs isolated per profile. The default policy:
| Item | Behavior | Reason |
|---|---|---|
| `history.jsonl` | Symlinked (shared) | Operator history is cross-profile |
| `sessions/` | Symlinked (shared) | Session index is cross-profile |
| `auth.json` | Isolated | OAuth tokens are profile-scoped |
| `.credentials` | Isolated | Credentials are profile-scoped |
| `config.toml` | Isolated | MCP server config is profile-scoped |
Symlinks are created only when the source exists in `~/.codex/`. If the source does not exist, the symlink is skipped silently (the runtime home still works).
config.toml Generation
When setting up a runtime home, AIWG:
1. Reads `~/.codex/config.toml` (if it exists) 2. Strips all `[mcp_servers.]` sections 3. Appends profile-scoped `[mcp_servers.]` blocks for each server in the profile
Each server block follows the Codex TOML format:
[mcp_servers.git-gitea]
command = "npx"
args = ["-y", "@gitea/mcp-server"]
env.GITEA_TOKEN = "..."
startup_timeout_sec = 10.0
tool_timeout_sec = 60.0
Non-MCP settings from the global config (model preferences, theme, keybindings) are preserved.
Runtime Home Management
Use AIWG to complete this documented outcome: Runtime Home Management
Have it inspect the current state, explain the plan, ask before material
changes, and report the result with verification evidence.
Troubleshooting
"Runtime home does not exist"
Error: Runtime home for profile "ops" does not exist.
Run "the agent-owned mcp operation profile add ops" or "the agent-owned session operation the provider option codex the profile option ops" to create it.
Run the agent-owned session operation once to initialize the runtime home.
Auth tokens not persisting across sessions
Verify the runtime home was created with the correct path:
ls ~/.codex/roles-runtime/ops/auth.json
If missing, the OAuth flow did not complete. Re-run the agent-owned session operation.
MCP servers not appearing in Codex
Check the generated config:
cat ~/.codex/roles-runtime/ops/config.toml
If empty or missing `[mcp_servers.*]` blocks, the profile may have no servers resolved. Run the agent-owned mcp operation to verify the server list.
Cross-device symlink errors
On systems where `~/.codex/` is on a different filesystem, symlinks may fail with `EXDEV`. AIWG suppresses this error and continues — the runtime home still works, but history and sessions will not be shared across profiles.
Further Reading
- MCP Profiles — Profile registry, CRUD, and provider overrides
- CLI Reference: mcp profile — Full command reference
- CLI Reference: session — Session launcher with profile support