Codex Cost Control skill for Codex

Know tokens per task, then cut the waste. Codex measures usage per task type with /status, /usage and a bundled script that totals codex exec --json usage events, identifies the biggest drivers (context churn, high effort, Fast tiers, subagents, noisy CI triggers), applies the cheapest levers first, and adds token budgets that fail automated runs loudly instead of draining limits overnight.

  • Codex Mastery
  • $codex-cost-control
  • 137 lines
  • 3 files
  • ~1,550 tokens
  • MIT
  • Updated

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- codex-cost-control

Installs to ~/.agents/skills/codex-cost-control/ for every project on this machine.

104-line POSIX script. No sudo, no telemetry. Read install.sh

Use it in Codex

Start a prompt with $codex-cost-control, or pick it from /skills. You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.

What Codex reads before loading it

Measures and reduces Codex token usage and spend with /status, /usage, codex exec --json usage events and a bundled usage summarizer, then cuts waste through model and effort choice, Fast and Ultrafast tier discipline, subagent and auto-review limits, leaner AGENTS.md and MCP context, prompt-cache-friendly sessions and CI trigger hygiene. Use when the user hits Codex usage limits, sees credits or API bills rise, wants a budget for automated Codex jobs, or asks how to make their plan's allowance last longer.

Use it when

  • Plan limits run out early or credits drain faster than expected
  • API-key Codex spend needs tracking or a budget
  • Automated or scheduled Codex jobs need a cost ceiling
  • Deciding whether Fast mode, ultra or subagents are worth it

Use something else when

How to check the result

Re-run the baseline tasks with scripts/sum_usage.py: average tokens per task drop while tests and review outcomes stay the same, and an over-budget run exits non-zero.

The mistake it prevents

Leaving Fast mode or high effort on by default for routine and automated work, multiplying usage on tasks that gain nothing from it.

~/.agents/skills/codex-cost-control/SKILL.md

--- name: codex-cost-control description: Measures and reduces Codex token usage and spend with /status, /usage, codex exec --json usage events and a bundled usage summarizer, then cuts waste… license: MIT ---

Cost is under control when you know tokens per task type, the expensive multipliers (Fast tiers, subagents, high effort, big context) are used on purpose, automation has a budget that fails loudly, and the cheapest setting that meets the quality bar is the default.

How Codex is billed

Sign-inWhat is consumedWhere to look
ChatGPT planPlan usage limits (time windows and weekly limits vary by plan), then credits if purchased/status (remaining limits), /usage daily/weekly/cumulative, the Codex usage dashboard
Business / Enterprise creditsWorkspace credits per token, per-user spend controlsWorkspace analytics, Analytics API
API key (CODEX_API_KEY, codex login --with-api-key)API token pricingAPI platform usage pages

ChatGPT Work and Codex share the same usage. Cloud tasks can consume more than local messages; reviews triggered on GitHub count as code review usage, local reviews count as general usage.

Multipliers to know

LeverEffect on usage
Fast mode (/fast, service_tier = "fast")2.5x included usage (2x purchased credits)
Ultrafast (eligible plans)8x included usage (6x credits)
ultra effort / subagentsEach subagent does its own model and tool work
Higher reasoning effortMore reasoning output tokens and longer turns
approvals_reviewer = "auto_review"Extra model calls per reviewed approval
Image generationUses limits roughly 3-5x faster
Every enabled MCP server, skill and AGENTS.md bytePaid on every turn as input

Workflow

- [ ] 1. Measure a baseline per task type
- [ ] 2. Find the top two cost drivers
- [ ] 3. Apply the cheapest effective levers
- [ ] 4. Put budgets on automation
- [ ] 5. Re-measure and record the new default

1. Measure

Interactive: run /status at the start and end of a representative task and note usage; /usage weekly for the trend.

Headless: always run with --json so usage is captured:

codex exec --json --sandbox workspace-write "$(cat task.md)" > runs/task-$(date +%s).jsonl
python3 scripts/sum_usage.py runs/*.jsonl
python3 scripts/sum_usage.py runs/*.jsonl --rates 1.25,0.125,10   # IN,CACHED,OUT per 1M, your price sheet

The script totals turn.completed usage per file (input, cached input, output, reasoning output), prints the cache-hit percentage, and with --rates an estimate in whatever unit you pass. cached_input_tokens is counted as part of input_tokens. SDK users get the same numbers from turn.usage.

2. Find the drivers

Symptom in the numbersLikely driver
Input tokens huge, cache% lowContext churn: new sessions for every small task, edited AGENTS.md mid-run, giant tool outputs
Input huge, cache% highLong sessions re-sending big history; heavy fixed preamble (MCP tools, skills, AGENTS.md)
Output or reasoning tokens highEffort too high for the task, verbose reports, ultra/subagents
Many turns per taskVague prompt without done-criteria, failing test loop, approvals bouncing
Spend spikes on scheduleCI or scheduled tasks firing on every push or too often

3. Levers, cheapest first

  1. Prompt precision. State files, constraints and done-criteria; ask for concise output (“report in 5 bullets”). Fewer turns beats every other lever.
  2. Model and effort. Use the lightest model and effort that pass (see codex-model-selection). Keep high effort and Astra for tasks that need them.
  3. Fixed context. Trim root AGENTS.md, nest package rules, disable unused MCP servers and skills (see codex-context-budget).
  4. Reading habits. Search before reading, read line ranges, cap command output with tail; a 5,000-line log read once stays in history.
  5. Session shape. Keep related work in one session so the stable prefix is cached; compact at milestones; start a new session for unrelated work rather than dragging a long history along.
  6. Tier discipline. Fast or Ultrafast only when someone is waiting; never for overnight or CI jobs.
  7. Fan-out discipline. Subagents, ultra and parallel worktrees only when the task splits into genuinely independent parts.
  8. Automation hygiene. Trigger CI Codex jobs on a label or failure, not every push; reduce scheduled task frequency; skip runs when nothing changed.

4. Budgets for automation

codex exec --json "$PROMPT" > run.jsonl
codex_status=$?
python3 scripts/sum_usage.py run.jsonl --budget 1500000 || {
  echo "Codex run exceeded token budget or produced no usage"; exit 1; }
exit "$codex_status"

--budget exits 2 when input plus output tokens exceed the limit, so the CI step fails and someone looks at the run. Enterprise admins can also set per-user spend controls. In the SDK, enforce the same with AbortSignal timeouts and a check on turn.usage.

5. Re-measure

Repeat the baseline tasks with the new settings; keep a change only if quality holds.

Gotchas

  • Reaching the limit mid-turn lets the current turn finish (subject to fair use); the next message waits for a reset or credits. Plan long jobs around reset times shown in /status.
  • An API key in the environment (CODEX_API_KEY) bills the API account, not the plan. Check codex login status when bills look wrong.
  • Retrying a failing codex exec in a loop multiplies cost with no new information; cap retries at one and inspect the log.
  • Memory generation, automatic review and recaps run extra model work in the background; disable what you do not use.
  • Credit and API prices change. Never hard-code a price sheet in docs; pass current rates to the script.

Output template

## Codex usage review (last 14 days)
| Task type | Runs | Avg tokens | Cache % | Main driver | Change | New avg |
|---|---|---|---|---|---|---|
| CI fix-forward | 42 | 1.9M | 31% | full log piped in | tail -n 300, Luna medium | 0.6M |
| Feature work | 18 | 3.4M | 78% | Fast mode always on | Fast only when pairing | -40% usage |
Budgets: CI job fails above 1.5M tokens (scripts/sum_usage.py --budget).

What the zip contains

Codex reads bundled files only when a step points to them. Every one is shown here, so you can review it before installing.

agents/openai.yamlgenerated from this page

Gives the Codex app a display name, a one-line description and a starter prompt for this skill.

interface:
  display_name: "Codex Cost Control"
  short_description: "Know tokens per task, then cut the waste"
  default_prompt: "$codex-cost-control I keep hitting my Codex usage limit by Wednesday, where is it going?"
scripts/sum_usage.py117 lines, 4.4 KB
#!/usr/bin/env python3
"""Sum token usage from `codex exec --json` event logs (JSONL).

Usage:
  python3 sum_usage.py run1.jsonl [run2.jsonl ...]
  python3 sum_usage.py runs/*.jsonl --rates 1.25,0.125,10 --budget 2000000
  codex exec --json "task" | tee run.jsonl | python3 sum_usage.py -

Reads every `turn.completed` event and totals its `usage` object per file.
`cached_input_tokens` is treated as a subset of `input_tokens`.
--rates IN,CACHED,OUT   price per 1M tokens (any unit: USD, credits) for an estimate.
--budget N              exit 2 if total tokens (input + output) exceed N.
Exit codes: 0 ok, 1 no usage found / bad input, 2 budget exceeded.
"""
import argparse
import json
import sys

FIELDS = ("input_tokens", "cached_input_tokens", "output_tokens", "reasoning_output_tokens")


def read_usage(stream, label):
    totals = dict.fromkeys(FIELDS, 0)
    turns = failed = bad = 0
    for line in stream:
        line = line.strip()
        if not line:
            continue
        try:
            event = json.loads(line)
        except json.JSONDecodeError:
            bad += 1
            continue
        kind = event.get("type")
        if kind == "turn.completed":
            turns += 1
            usage = event.get("usage") or {}
            for key in FIELDS:
                value = usage.get(key) or 0
                if isinstance(value, (int, float)):
                    totals[key] += int(value)
        elif kind == "turn.failed":
            failed += 1
    return {"label": label, "turns": turns, "failed": failed, "bad_lines": bad, **totals}


def estimate(row, rates):
    if not rates:
        return None
    rate_in, rate_cached, rate_out = rates
    uncached = max(row["input_tokens"] - row["cached_input_tokens"], 0)
    return (uncached * rate_in + row["cached_input_tokens"] * rate_cached
            + row["output_tokens"] * rate_out) / 1_000_000


def parse_rates(text):
    parts = [float(p) for p in text.split(",")]
    if len(parts) != 3:
        raise argparse.ArgumentTypeError("--rates needs IN,CACHED,OUT")
    return parts


def main():
    parser = argparse.ArgumentParser(description="Total token usage from codex exec --json logs.")
    parser.add_argument("files", nargs="+", help="JSONL files, or - for stdin")
    parser.add_argument("--rates", type=parse_rates, help="IN,CACHED,OUT price per 1M tokens")
    parser.add_argument("--budget", type=int, help="fail (exit 2) above this many total tokens")
    args = parser.parse_args()

    rows = []
    for name in args.files:
        if name == "-":
            rows.append(read_usage(sys.stdin, "<stdin>"))
            continue
        try:
            with open(name, encoding="utf-8") as handle:
                rows.append(read_usage(handle, name))
        except OSError as err:
            print(f"error: cannot read {name}: {err}", file=sys.stderr)
            return 1

    header = f"{'file':40} {'turns':>5} {'input':>11} {'cached':>11} {'cache%':>6} {'output':>9} {'reason':>9}"
    if args.rates:
        header += f" {'est.cost':>10}"
    print(header)
    total = {"label": "TOTAL", "turns": 0, "failed": 0, "bad_lines": 0, **dict.fromkeys(FIELDS, 0)}
    for row in rows + [None]:
        if row is None:
            row = total
        else:
            for key in ("turns", "failed", "bad_lines", *FIELDS):
                total[key] += row[key]
        pct = 100 * row["cached_input_tokens"] / row["input_tokens"] if row["input_tokens"] else 0
        line = (f"{row['label'][-40:]:40} {row['turns']:>5} {row['input_tokens']:>11,} "
                f"{row['cached_input_tokens']:>11,} {pct:>5.0f}% {row['output_tokens']:>9,} "
                f"{row['reasoning_output_tokens']:>9,}")
        cost = estimate(row, args.rates)
        if cost is not None:
            line += f" {cost:>10.4f}"
        print(line)

    if total["failed"]:
        print(f"note: {total['failed']} failed turn(s) found", file=sys.stderr)
    if total["bad_lines"]:
        print(f"note: {total['bad_lines']} non-JSON line(s) skipped", file=sys.stderr)
    if total["turns"] == 0:
        print("error: no turn.completed events found (was the run started with --json?)", file=sys.stderr)
        return 1
    used = total["input_tokens"] + total["output_tokens"]
    if args.budget is not None and used > args.budget:
        print(f"BUDGET EXCEEDED: {used:,} tokens > {args.budget:,}", file=sys.stderr)
        return 2
    return 0


if __name__ == "__main__":
    sys.exit(main())

About this skill

Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.

esc