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.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- codex-cost-control Installs to ~/.agents/skills/codex-cost-control/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install codex-cost-control The CLI also searches, updates and removes skills. Run it with no arguments to pick from a list. Needs Node 20.12+.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- --project codex-cost-control Installs to .agents/skills/codex-cost-control/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/codex-cost-control.zipunzip -o codex-cost-control.zip -d ~/.agents/skills/ The archive holds the skill folder with its generated agents/openai.yaml. Unzip into .agents/skills/ to scope it to a repo.
& ([scriptblock]::Create((irm codexskills.dev/install.ps1))) codex-cost-control PowerShell 5.1 or 7. Installs to %USERPROFILE%\.agents\skills\. Add -Project for the current repo.
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
- The problem is context quality, not spend: use codex-context-budget
- Choosing a model for one task type: use codex-model-selection
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.
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-in | What is consumed | Where to look |
|---|---|---|
| ChatGPT plan | Plan 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 credits | Workspace credits per token, per-user spend controls | Workspace analytics, Analytics API |
API key (CODEX_API_KEY, codex login --with-api-key) | API token pricing | API 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
| Lever | Effect 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 / subagents | Each subagent does its own model and tool work |
| Higher reasoning effort | More reasoning output tokens and longer turns |
approvals_reviewer = "auto_review" | Extra model calls per reviewed approval |
| Image generation | Uses limits roughly 3-5x faster |
| Every enabled MCP server, skill and AGENTS.md byte | Paid 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 numbers | Likely driver |
|---|---|
| Input tokens huge, cache% low | Context churn: new sessions for every small task, edited AGENTS.md mid-run, giant tool outputs |
| Input huge, cache% high | Long sessions re-sending big history; heavy fixed preamble (MCP tools, skills, AGENTS.md) |
| Output or reasoning tokens high | Effort too high for the task, verbose reports, ultra/subagents |
| Many turns per task | Vague prompt without done-criteria, failing test loop, approvals bouncing |
| Spend spikes on schedule | CI or scheduled tasks firing on every push or too often |
3. Levers, cheapest first
- Prompt precision. State files, constraints and done-criteria; ask for concise output (“report in 5 bullets”). Fewer turns beats every other lever.
- 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.
- Fixed context. Trim root AGENTS.md, nest package rules, disable unused MCP servers and skills (see codex-context-budget).
- Reading habits. Search before reading, read line ranges, cap command output with
tail; a 5,000-line log read once stays in history. - 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.
- Tier discipline. Fast or Ultrafast only when someone is waiting; never for overnight or CI jobs.
- Fan-out discipline. Subagents,
ultraand parallel worktrees only when the task splits into genuinely independent parts. - 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. Checkcodex login statuswhen bills look wrong. - Retrying a failing
codex execin 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.yaml
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.py
#!/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.