Get API key →

Why monthly caps don't protect you from one bad LLM run

May 2026 · 8 min read

Four people had a spend cap in force when a run went wrong. The caps held. The bills came anyway. Each report below is one or two sentences in the reporter's own words, with the link, the date and the state of the thread, so you can read the rest yourself and disagree with what we make of it.

None of the four caps had a bug. Each did what it is bound to do. This post is about what that is.

Four runs, four caps that were in force

A $3,000 monthly limit. One weekend.

“~$300 of unintended API usage over a single weekend” on an “Enterprise plan ($3,000/month limit)”.

claude-code issue 64744, opened June 2, 2026. Open, labelled bug and area:cost. github.com/anthropics/claude-code/issues/64744

The limit is a month. The incident was a weekend, and $300 never came near $3,000: there was nothing to cross. The loop ran inside the tool's own daemon and made its own provider calls, which matters further down. A ceiling governs a call where the call asks.

A weekly plan allowance and a spending cap. One evening.

“My Codex run consumed the remainder of my weekly pro plan allowance (80%+) and +$700 of additional usage credits before my spending cap finally stopped it. Effectively zero work was done.”

OpenAI Developer Community, July 30, 2026. One user's report, not confirmed by OpenAI; the thread is open and its two replies argue with it. community.openai.com/t/1388493

The cap fired. It fired when the account crossed its threshold, which is what an account cap is for, and by then the run had spent the plan and $700 on top of it.

A $600 organization spend limit. One day.

“One session hit its ChatGPT subscription usage limit and, instead of stopping, wrote batch runner scripts that called the metered API directly.”

“The billing export confirmed 1,917 requests and 62.2 million tokens in a single UTC day (July 19), roughly $453 of metered usage, three automatic card recharges, and a July bill of $812.47 against a configured $600 organization spend limit.”

OpenAI Developer Community, August 4, 2026. One user's report; OpenAI Support closed the thread on August 13, 2026 while an internal review was open. community.openai.com/t/1389087

The reporter says the keys sat in a .env file in the working directory, and the thread's own verdict is that the setup was at fault, not the platform. It is here for a narrower reason. The subscription limit was bound to the session, the spend limit to the organization and the month, and the runner that did the spending answered to neither. A ceiling on a job governs the calls that pass through its check. A script that calls the provider directly never asks, so it is never refused.

A session window. Two runs.

“Once an agent dies with You've hit your session limit · resets <time>, that error is terminal for the whole window — every subsequent spawn will hit it too. The orchestrator treats it as a per-agent failure instead, and keeps feeding the queue.”

claude-code issue 94012, opened September 13, 2026. Open, labelled bug and has repro, no maintainer reply yet; one user's report, counted in tokens and agents rather than dollars. github.com/anthropics/claude-code/issues/94012

Here the cap is bound to a clock. The window refused every agent, and the harness kept spawning into it, because a refusal tied to a reset time says nothing about the job that is asking.

What each cap is bound to

The vendors say it themselves. The sentences below are quoted, not summarised, so the boundary each one names is theirs.

OpenAI

“Enforcement is not instantaneous. The API Platform can process a small amount of extra usage while the limit state propagates, so recorded spend can slightly exceed the configured amount.”

“An organization hard limit applies to API traffic across all projects in the organization.”

OpenAI, Spend limits, developer documentation. developers.openai.com/api/docs/guides/spend-limits

Anthropic

“Once you reach your tier's spend cap, API usage pauses until 00:00 UTC on the first day of the next month, unless you request a higher limit sooner.”

“Retrying, including the SDKs' automatic retries, fails until access resumes.”

Anthropic, Rate limits, developer documentation. platform.claude.com/docs/en/api/rate-limits

Google

“Long-running tasks like batch mode completions and agent sessions may incur overages beyond your project spend cap.”

“Billing data processing times can be delayed in AI Studio, up to around 10 minutes.”

Google, Gemini API billing. ai.google.dev/gemini-api/docs/billing

Three vendors, one shape. The boundary is the organization, the project or the billing account. The unit is a calendar month, or the minutes a billing pipeline needs to catch up. The enforcement lags the spend by design, and each page says so. None of that is a defect. A cap bound to an account over a month has nothing to cross at the scale one runaway job operates on, and when a job does push the account across, the whole account waits for the reset, retries included.

OpenAI's own Cookbook draws the conclusion in one sentence:

“Organization and project spending limits cover overall usage, but they cannot tell you whether that task can afford its next request.”

OpenAI Cookbook, Build a per-run spending controller with the Responses API, August 17, 2026. developers.openai.com/cookbook/articles/per_run_spending_controller_responses_api

A ceiling on the job

AgentBill binds the ceiling to the job. You name the job with a task_ref and set its ceiling once, in the console or with PUT /tasks/:task_ref/ceiling and ceiling_units in the body. From then on every call that passes that task_ref draws from the same ceiling: different processes, different providers, same job. Before each provider call, preflight asks whether this job has units left for this one. When it does not, the answer is approved: false with the reason task_ceiling_exceeded, the SDK raises, and your code decides what happens next: wait and retry, fall back to something cheaper, hand the job to a person, or let it end there. AgentBill is an SDK inside your process, not a proxy. No base URL changes, no traffic routed through us, no provider keys held. If we are unreachable, the SDK raises inside your process and, again, your code decides.

What this does not govern, because two of the four reports above turn on it: a call that never passes through preflight. A script that calls the provider with keys from a .env file does not ask. A loop inside a tool's own daemon does not ask. The ceiling covers the calls your code routes through it and nothing else. That is a smaller promise than a cap on the account, and it is the one a runaway job actually tests.

The same run, with a task_ref

Take the weekend loop. The job is job-142, its ceiling is 500 units, set in the console before the run starts. Run 1: preflight asks, the answer is approved, the provider call goes out, the units are recorded. Run 2, the retry: preflight asks against the same task_ref, approved, units remain. This continues while units remain. Run 42: the job is at 492 of 500 and this call asks for 12. Preflight answers approved: false, the SDK raises TaskCeilingExceededError, and your code decides.

The retry bug is still there. What it can no longer do is compound. Every call of the job asks the same ceiling, and the ceiling was set before the night began, not read off the bill in the morning.

Where the recipe leaves off

The Cookbook article quoted above is a working recipe for the same idea: give each run a budget, reserve before the call, settle after it. Its closing section says what it leaves out. The lock “protects one Python process”, and for several workers it points you to “a shared store that checks and reserves the budget in one operation”. That shared, atomic reservation is the part that is hard to get right under concurrency, and it is what the preflight endpoint is. How it avoids reserving twice for one retry, and why settlement is by the recorded amount, is in the post on double-billing under concurrent load.

Implementing it

The pattern is the same whatever runs inside the agent: LangChain, the OpenAI Agents SDK, AutoGen, a hand-written loop. You are wrapping the provider call, not the internals. Set the ceiling in the console, then the code names the job and nothing about its budget.

Python

pip install agentbill-sdk
from agentbill import AgentBillClient, TaskCeilingExceededError

client = AgentBillClient(api_key="agb_your_key")

# job-142 has a ceiling of 500 units, set in the console.
# Every call that passes this task_ref draws from the same one.
try:
    client.preflight(agent_id="researcher", task_ref="job-142", estimated_units=12)
except TaskCeilingExceededError:
    # approved: false. The job is out of units. Your code decides:
    # wait, fall back, hand off, or let the job end here.
    raise

# your provider call goes here

# settle, or the units stay held until the reservation expires
client.record(agent_id="researcher", task_ref="job-142", units=12)

Node.js

npm install agentbill
import { preflight, record, TaskCeilingExceededError } from 'agentbill'  // reads AGENTBILL_API_KEY

// job-142 has a ceiling of 500 units, set in the console.
// Every call that passes this taskRef draws from the same one.
try {
  await preflight({ agentId: 'researcher', taskRef: 'job-142', estimatedUnits: 12 })
} catch (e) {
  if (e instanceof TaskCeilingExceededError) {
    // approved: false. The job is out of units. Your code decides.
  }
  throw e
}

// your provider call goes here

// settle, or the units stay held until the reservation expires
await record({ agentId: 'researcher', taskRef: 'job-142', units: 12 })

Summary

A monthly cap fires on a calendar, over an account, and the vendors document the lag. A preflight check answers before one call goes out, on a ceiling you named for this job, and your code decides what to do with the answer. They are bound to different things. Keep the account cap for drift. Give the job a ceiling for the night you are not watching.

If you are running agents that loop, retry or run unattended, the check that matters is the one that answers before the next call, not the statement that arrives after the last one.

Add preflight to your agents

Free tier: 1,000 preflight calls/month. No credit card required.

Get API key →