{
  "id": "codex-cli",
  "title": "Codex CLI Cookbook",
  "lede": "Recipes built on what Codex leans into: a sandbox you configure rather than negotiate with, subagents declared as TOML, and a `codex exec` whose stdout is clean enough to pipe.",
  "updated": "2026-07-12",
  "categories": [
    { "id": "sandbox", "label": "Sandbox & approvals" },
    { "id": "agents", "label": "Subagents" },
    { "id": "config", "label": "Config & profiles" },
    { "id": "headless", "label": "Headless & CI" }
  ],
  "recipes": [
    {
      "id": "read-only-audit",
      "title": "Audit a repo you don't trust yet",
      "category": "sandbox",
      "difficulty": "starter",
      "when": "You cloned something and want Codex to explain it before anything in it gets a chance to run.",
      "sheetRefs": ["sandbox", "flags"],
      "steps": [
        {
          "text": "Pin the sandbox to `read-only` for the run. It's the mode where Codex can look but not touch — no writes, no network.",
          "code": {
            "lang": "bash",
            "text": "codex exec \\\n  --sandbox read-only \\\n  \"Map this repo: entry points, where it talks to the network, anything that runs at install time.\""
          }
        },
        {
          "text": "When you do want writes, `workspace-write` scopes them to the working directory rather than the whole filesystem.",
          "code": {
            "lang": "bash",
            "text": "codex --sandbox workspace-write \"Fix the failing tests.\""
          }
        }
      ],
      "why": "The three modes are `read-only`, `workspace-write`, and `danger-full-access` — and the third is named that way on purpose. Reaching for it because a tool call got blocked is how a supply-chain payload in a postinstall script gets its wish.",
      "links": [
        { "label": "Sandboxing", "url": "https://developers.openai.com/codex/sandboxing" }
      ]
    },
    {
      "id": "approval-policy",
      "title": "Decide once how much you want to be asked",
      "category": "sandbox",
      "difficulty": "starter",
      "when": "You are either being interrupted constantly, or not enough — and you'd rather set the dial than fight it per-command.",
      "sheetRefs": ["sandbox", "config"],
      "steps": [
        {
          "text": "Set the two dials together in `config.toml`. Approvals and sandbox are separate knobs: the sandbox decides what's *possible*, the approval policy decides what you get *asked about*.",
          "code": {
            "lang": "toml",
            "file": "~/.codex/config.toml",
            "text": "# Sensible default: work freely inside the repo, ask before anything wider.\nsandbox_mode = \"workspace-write\"\napproval_policy = \"on-request\"\nmodel_reasoning_effort = \"medium\""
          }
        },
        {
          "text": "Override per-run from the flags when a task genuinely needs something different.",
          "code": {
            "lang": "bash",
            "text": "codex --ask-for-approval never --sandbox read-only \\\n  \"Summarize the architecture. Don't change anything.\""
          }
        }
      ],
      "why": "`--ask-for-approval never` is safe *because* it is paired with `read-only` — never-ask plus full-access is the combination that ends badly. Treat the two flags as one decision, not two.",
      "links": [
        { "label": "Approvals & security", "url": "https://developers.openai.com/codex/agent-approvals-security" }
      ]
    },
    {
      "id": "toml-subagents",
      "title": "Declare a reviewer once, reuse it forever",
      "category": "agents",
      "difficulty": "intermediate",
      "when": "You keep re-typing the same \"review this like an owner, focus on X\" preamble and getting a slightly different reviewer each time.",
      "sheetRefs": ["agents", "config"],
      "steps": [
        {
          "text": "Define the subagent as TOML. Giving it its own `sandbox_mode` is the useful part: an explorer that *cannot* write can't wander off and start fixing things.",
          "code": {
            "lang": "toml",
            "file": "~/.codex/agents/pr_explorer.toml",
            "text": "name = \"pr_explorer\"\ndescription = \"Read-only codebase explorer for gathering evidence before changes are proposed.\"\nmodel_reasoning_effort = \"medium\"\nsandbox_mode = \"read-only\"\ndeveloper_instructions = \"\"\"\nGather evidence, propose nothing.\nCite every claim as file:line. If you cannot cite it, don't say it.\n\"\"\""
          }
        },
        {
          "text": "Bound the fan-out globally so a recursive spawn can't run away with your quota.",
          "code": {
            "lang": "toml",
            "file": "~/.codex/config.toml",
            "text": "[agents]\nmax_threads = 6\nmax_depth = 1"
          }
        }
      ],
      "why": "`max_depth = 1` is the guardrail worth setting on day one: it stops subagents from spawning subagents, which is the failure mode that turns one prompt into a hundred and is invisible until the bill arrives.",
      "links": [
        { "label": "Subagents", "url": "https://developers.openai.com/codex/agent-configuration/subagents" }
      ]
    },
    {
      "id": "parallel-review",
      "title": "Split a PR review across parallel agents",
      "category": "agents",
      "difficulty": "intermediate",
      "when": "One reviewer on a big branch finds the obvious things and misses the interesting ones.",
      "sheetRefs": ["agents"],
      "steps": [
        {
          "text": "Ask for one agent per concern, then a synthesis. Naming the concerns is what makes them independent — an unprompted \"review this\" collapses back into one perspective.",
          "code": {
            "lang": "text",
            "text": "Review this branch against main with parallel subagents.\nSpawn one agent per point, wait for all of them, then summarize\nthe result for each with file references:\n\n1. Security\n2. Race conditions\n3. Test gaps\n4. Maintainability"
          }
        }
      ],
      "why": "The \"wait for all of them, then summarize\" clause matters more than it looks: without it you get a stream of partial findings and no synthesis, which is more to read and less to act on.",
      "links": [
        { "label": "Subagents", "url": "https://developers.openai.com/codex/agent-configuration/subagents" }
      ]
    },
    {
      "id": "profiles-per-context",
      "title": "Keep one config per kind of work",
      "category": "config",
      "difficulty": "intermediate",
      "when": "Your CI runs, your spikes, and your production repo all want different defaults, and you're tired of remembering which flags to pass.",
      "sheetRefs": ["config", "flags"],
      "steps": [
        {
          "text": "Put shared defaults in `config.toml` and let each profile carry only what differs.",
          "code": {
            "lang": "toml",
            "file": "~/.codex/ci.config.toml",
            "text": "sandbox_mode = \"read-only\"\napproval_policy = \"never\"\nmodel_reasoning_effort = \"low\""
          }
        },
        {
          "text": "Select it by name.",
          "code": {
            "lang": "bash",
            "text": "codex exec --profile ci \"Check the diff for missing tests.\""
          }
        }
      ],
      "why": "Precedence runs flags → profile → `config.toml`, so a profile is a *named set of overrides*, not a full config. Keep them thin: the more a profile restates, the more places you have to remember to change.",
      "links": [
        { "label": "Config basics", "url": "https://developers.openai.com/codex/config-file/config-basic" }
      ]
    },
    {
      "id": "exec-in-pipeline",
      "title": "Pipe Codex into another command",
      "category": "headless",
      "difficulty": "starter",
      "when": "You want the output of a Codex run to become the input of something else, without progress chatter polluting it.",
      "sheetRefs": ["launch", "flags"],
      "steps": [
        {
          "text": "`codex exec` streams progress to **stderr** and prints only the final agent message to **stdout** — so a plain pipe just works, no flag needed.",
          "code": {
            "lang": "bash",
            "text": "codex exec \"generate release notes for the last 10 commits\" | tee release-notes.md"
          }
        },
        {
          "text": "Use `--ephemeral` when the run should leave no session behind.",
          "code": {
            "lang": "bash",
            "text": "codex exec --ephemeral \"triage this repository and suggest next steps\""
          }
        }
      ],
      "why": "That stdout/stderr split is the whole reason `codex exec` composes with Unix. It means you can `| tee`, `| jq`, or `$( )` a run without first teaching a parser to skip the spinner.",
      "links": [
        { "label": "Non-interactive mode", "url": "https://developers.openai.com/codex/non-interactive-mode" }
      ]
    }
  ]
}
