Skip to content

Codex hits its usage limit and the run FAILS — the one runtime whose limit signal is actually structured #222

Description

@jitokim

Found by hands-on QA of the Codex path, 2026-08-21.

What happened

$ oh-my-graph --runtime codex auto "…" --plan-only --no-web
Codex runtime: Claude agent mapping and skill activation are unavailable; …
Planning a graph for goal "…"...
planning failed after spending unknown — a planner call is paid for whether or not its graph loads.
oh-my-graph: graph planning failed: planner exited with code 1

The account had simply run out of quota. Reproduced directly against the CLI
with the same argv shape oh-my-graph builds:

{"type":"error","message":"You've hit your usage limit. Upgrade to Plus to continue using Codex (…), or try again at Sep 13th, 2026 10:04 PM."}
{"type":"turn.failed","error":{"message":"You've hit your usage limit. …"}}

So a limit was reported to the user as planner exited with code 1
indistinguishable from a broken planner, a bad goal, or a malformed reply.

Why this is worth more than a nicer message

ADR 0009 decided that a session limit is a pause, not a failure: the run
drains, records the limited node nowhere, and resume --retry-failed picks it up
later. It scoped that to Claude on purpose (§Scope, settled 2026-08-15 closing
#171), and gave a precise reason: detection is prose matching against Claude's
wording
, so there is nothing for another runtime's message to match. The
classification is gated at internal/runner/cli.go:294
(if r.protocol.runtime() == RuntimeClaude).

That reasoning was right when it was written and it does not hold for Codex,
because Codex's limit is not prose. It is a typed JSONL record —
{"type":"turn.failed","error":{"message":…}} — arriving on the same stream
parseCodexJSONL already decodes.

ADR 0009 wrote its own revisit condition, at docs/adr/0009-*.md:70:

Should a future runtime expose a structured limit signal, that is a reason
to revisit — and it would be a better foundation than this one, since the whole
mitigation list below exists to survive matching prose.

That condition is now met. The runtime the pause was withheld from is the one
with the better signal.

The asymmetry, stated plainly

Claude Codex
limit signal prose only typed JSONL record
engine treats it as pause, resumable ordinary failure

The runtime that is harder to detect gets the good behaviour; the one that is
easy to detect gets none.

Not proposing the fix here

Two things worth deciding separately, and neither is obvious:

  1. Whether the pause should extend to Codex at all, or whether ADR 0009's
    narrowing should simply be re-argued now that its stated premise has changed.
  2. Whether a planner/assessor limit deserves the same treatment as a node
    limit. Today the pause covers scheduled nodes; this failure was in the
    planner, before any node existed, and there is no run to resume.

Related: #214 gave the assessor a bounded retry for spawn failures on exactly
the grounds that no model was reached. A limit is the mirror case — the model
was reached and refused — and it is equally not a verdict.

Also observed in the same session (not defects)

  • The auto pre-run disclosure does name the gap: "Codex runtime: Claude
    agent mapping and skill activation are unavailable"
    . Good, and it matches
    chat's.
  • internal/runner/codex_protocol.go carries no PluginDirs and no Agent
    handling at all, so skills and agents cannot reach a Codex node even by
    accident. The disclosure above is therefore accurate rather than aspirational.

Blocked

Further hands-on Codex QA is blocked until the quota resets (the CLI says
Sep 13), which is itself a finding worth writing down: this project cannot
smoke-test its second runtime on demand.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions