Course · Codex · Automation
Make CI runs reproducible with --ignore-user-config and --ignore-rules
The CI job from the last lesson runs, authenticates, and fails shut on schedule - and it still carries a hidden dependency. Every codex exec call, local or in CI, picks up layers from whatever machine happens to run it: your personal config under $CODEX_HOME (~/.codex/ by default), and the execpolicy .rules files that decide which shell commands Codex may run unprompted. That’s exactly the right default when you’re the one running it.
It becomes a liability the instant the run is supposed to mean the same thing everywhere - which is the whole premise of an unattended check.
Why the same command drifts
Section titled “Why the same command drifts”Your ~/.codex/config.toml holds your defaults: the sandbox you reach for, your model, your effort setting. What it does not hold is your profiles - a profile lives in its own layer file, $CODEX_HOME/<name>.config.toml, pulled in only when you pass --profile <name> (the approvals chapter built two that way). Your budget-build is ~/.codex/budget-build.config.toml, not a table inside config.toml.
None of that exists on the runner - and if it somehow did, by way of a stray home directory or a teammate triggering the job from their own machine, it would be their defaults standing in for a shared gate. Your .rules file is the same story: a per-machine command policy quietly voting on what a check every contributor relies on is allowed to run.
The same commit and the same prompt can produce different behavior on two machines, not because the code changed, but because the machine did. For a check you’re trusting to catch a bad import before it corrupts months of history, that drift is exactly the thing worth killing.
The committed brief
Section titled “The committed brief”Here’s the trick this lesson turns on, and it’s the sharpest form the rules-file half of this chapter’s contract takes: only what’s committed gets to count as this run’s instructions. Your AGENTS.md - 21 lines, about 115 estimated tokens loaded every session, the file the rules chapter audited and extended - is checked into the repo, so the commit pins it exactly the way it pins the code.
Your personal config.toml and your machine’s .rules file aren’t committed anywhere. They’re opinions about how you like to work, not facts about what budgetcli (the course’s running example, a small self-hosted personal-finance API in Python and FastAPI) needs. A reproducible run keeps the first and strips the second two:
codex exec "Review this push against the money rule:amounts reach integer cents without passing through a float." \ --ignore-user-config \ --ignore-rules \ -c approval_policy=never \ --sandbox read-only--ignore-user-configstops Codex loading$CODEX_HOME/config.toml. The run inherits no personal defaults - only the flags you pass explicitly. Authentication still resolves throughCODEX_HOME, so ignoring the config doesn’t break the run’s key.--ignore-rulesskips the user and project execpolicy.rulesfiles - the command policy, not the instructions. Blast radius is then decided entirely by the sandbox and approval flags written into the workflow, not by a policy file that might have changed since you last looked.
What that second flag does and doesn’t take with it
Section titled “What that second flag does and doesn’t take with it”Read the name carefully, because it invites a costly misreading. --ignore-rules means the execpolicy .rules files - command policy. It does not touch AGENTS.md: the rules chain still loads, so budgetcli’s non-negotiables - money is integer cents, never floats; dates are ISO 8601 UTC - still reach the agent every run.
That’s almost always what you want, and it’s exactly why the AGENTS.md chain is safe to depend on here in a way nothing else is: it’s committed, so the commit pins it the same way it pins the code.
The trade you’re actually making is about permission, not instruction. Strip the .rules files and the sandbox-and-approval settings become the only thing bounding what the run may execute - which is why the command above states --sandbox read-only and -c approval_policy=never outright instead of letting either resolve against a default. What a run depends on should be sitting in the workflow file where you can see it, not three directories up in a file nobody diffed.
Restating the cents rule in the prompt, as the example does, is belt-and-braces on top of the committed brief, not a replacement for it - a cheap habit worth keeping on anything that touches money.
Whether you reach for both flags is a judgment call, not a rule. --ignore-user-config is almost always right in CI - personal preference has no business deciding a shared gate. --ignore-rules is worth reaching for when a machine-local command policy shouldn’t get a vote in what that gate executes; if your project’s .rules file is itself committed, stable, and something you want honored, keep it - knowing the trade: the flag skips user and project .rules together, so keeping it off re-admits per-machine user drift (most acutely in a teammate-triggered run). The decision is wholesale, not per layer; if you need the project policy honored without the user policy, put the project policy in committed configuration the run reads explicitly.
The honest default for a check you’ll trust unattended: strip the user config, decide the rules flag on its merits, and pin the sandbox and approval flags explicitly either way.
When the org owns the policy instead of you
Section titled “When the org owns the policy instead of you”One layer above all of this, on plans that support it: Codex has a managed-configuration layer an administrator can enforce across the CLI, app, and IDE, backed by a requirements.toml org-policy file constraining the security-sensitive settings - approval policy, sandbox mode, web-search and MCP allowlists.
Where it’s in force, no unattended run, however it was launched, can set itself a looser sandbox or a more permissive approval mode than policy allows; a conflicting setting falls back to a compatible value and Codex says so. You don’t need any of this to ship budgetcli’s check, but it’s the lever an organization pulls to make the posture you’ve been building by hand the default, not something every engineer has to remember alone.
Your CI run is more reproducible now - fewer user- and project-policy surprises - but “reproducible” still has edges this lesson didn’t close: the CLI version, the model, dependencies, hooks, MCP servers, the runner image, network access, any managed policy. Pin those too whenever they affect the result. What that covers is the whole shape a single codex exec call can carry: one prompt, one result, one exit code. The last lesson is about the job that outgrows that shape entirely.