The short version: my token budget guard refunded the same reservation on every retry. The counter drifted upward, the batch ran past its cap, and half the shards died at the worst moment. The fix was one rule: a reservation can be closed exactly once.
One note before we start. The log shapes below are rebuilt from a synthetic repro, not a customer run. The pattern is real. The numbers are illustrative.
The alert said 78%, the provider said 100%
The batch stopped at 04:10. Three of twelve shards failed on quota errors.
My local counter showed 78% of the budget consumed. The provider dashboard showed 100%.
Both numbers came from the same run. Only one of them was real.
So which one do you trust when the tool you built is the thing lying to you?
Not the counter. Never the counter. Trust the ledger.
Step 1: Stop counting, start logging
A single integer cannot be debugged. It has no history and no owner.
So I replaced the counter with an append-only event log. Every state change writes one line.
import json, time
def log(path, ev, rid, amount):
with open(path, "a") as f:
f.write(json.dumps({"ev": ev, "rid": rid, "amount": amount, "t": time.time()}) + "\n")
Three event types. reserve when a call starts. settle when real usage is known. refund when nothing was consumed.
The rid field matters most. It is the reservation identity, and it is where the bug was hiding.
Step 2: Replay the ledger instead of rereading the code
Reading your own code makes you defend it. Replaying events makes the code confess.
I wrote 20 lines that fold the log into a final number.
#!/usr/bin/env python3
import json, sys, collections
events = [json.loads(l) for l in open(sys.argv[1])]
closes = collections.Counter(e["rid"] for e in events if e["ev"] in ("refund", "settle"))
print("reserves", sum(1 for e in events if e["ev"] == "reserve"))
print("refunds ", sum(1 for e in events if e["ev"] == "refund"))
print("settles ", sum(1 for e in events if e["ev"] == "settle"))
print("net ", sum(e["amount"] for e in events))
print("multi-closed rids", {r: c for r, c in closes.items() if c > 1})
The replay printed 412 reserves and 449 refunds. More refunds than reserves.
No accounting system can refund more often than it reserves. That is the entire bug, visible in one line of output.
python replay.py run.jsonl | tail -3
# refunds 449
# settles 0
# multi-closed rids: 37 entries
Zero settles. Interesting. The happy path never reconciled at all.
Step 3: Find both refund call sites
Here is the retry wrapper as it shipped.
def call_with_retry(client, prompt, est, budget):
for attempt in range(3):
try:
return client.complete(prompt)
except TransientError:
budget.refund(est) # refund A: per failed attempt
time.sleep(2 ** attempt)
raise QuotaExceeded("gave up")
And here is the caller.
def guarded_call(client, prompt, budget):
est = estimate(prompt) + MAX_OUTPUT
budget.reserve(est)
try:
return call_with_retry(client, prompt, est, budget)
except Exception:
budget.refund(est) # refund B: on final failure
raise
Two refunds for one reservation. A transient timeout refunds. The outer handler refunds again.
Two retries plus a final failure equals three refunds against one reserve. The budget grows by two estimates.
Is that a retry bug or an accounting bug? It is an ownership bug. Nobody owned the reservation.
Step 4: Make close idempotent
The fix is small. A reservation can be closed exactly once, and the closer must present the rid.
import uuid
class Budget:
def __init__(self, limit, path):
self.limit, self.spent, self.path = limit, 0, path
self.closed = set()
def reserve(self, est):
rid = uuid.uuid4().hex[:12]
self.spent += est
log(self.path, "reserve", rid, est)
return rid
def _close(self, rid, ev, amount):
if rid in self.closed:
return False # second close is a no-op
self.closed.add(rid)
self.spent += amount
log(self.path, ev, rid, amount)
return True
def refund(self, rid, est):
return self._close(rid, "refund", -est)
def settle(self, rid, est, actual):
return self._close(rid, "settle", actual - est)
Now the caller owns the reservation, and the retry loop does not touch it.
def guarded_call(client, prompt, budget):
est = estimate(prompt) + MAX_OUTPUT
rid = budget.reserve(est)
try:
resp = call_with_retry(client, prompt)
except Exception:
budget.refund(rid, est)
raise
budget.settle(rid, est, resp.usage.total_tokens)
return resp
Notice what changed in the retry path. A timed-out attempt keeps its reservation open.
Why? Because a timeout does not mean zero tokens. The server may have generated the full completion.
Treating that as free is how you get a surprise. Write the assumption down in a comment.
Step 5: Pin the invariant with a test
Every ledger needs one assertion that must never fail.
def test_counter_matches_ledger(tmp_path):
b = Budget(limit=10_000, path=tmp_path / "l.jsonl")
rid = b.reserve(800)
assert b.refund(rid, 800) is True
assert b.refund(rid, 800) is False # idempotent
events = [json.loads(l) for l in open(b.path)]
assert b.spent == sum(e["amount"] for e in events) == 0
Run it on every change to the retry layer. That layer is where accounting goes to die.
pytest -q tests/test_budget.py -k ledger
Step 6: Resume from checkpoints, not from zero
A quota stop is not a crash. It is a pause. Treat it like one.
Each shard writes its cursor after every completed item.
import json, os
def save(path, state):
tmp = path + ".tmp"
open(tmp, "w").write(json.dumps(state))
os.replace(tmp, path) # atomic on POSIX
On restart, read the cursor and skip finished work. The rerun cost 4% of the original budget instead of 100%.
Running the replay harness somewhere cheap
The replay is light on CPU and heavy on output. I stopped running it on my laptop.
That is where MonkeyCode fit in. It is an open-source project with free model access and a free server option, which is enough to host a nightly replay job and a log summarizer. The operator states the free tier includes a 10M-token allowance; confirm the current terms in the console before you plan around it.
Disclosure: This article was prepared as part of MonkeyCode's product outreach.
What I used the model for was boring and useful. It turns a 400-line ledger into a five-line summary of which rids closed twice. That is a good fit for a free tier. It is a terrible fit for anything needing a hard spend guarantee.
Where this approach breaks
- A client-side ledger cannot cap provider-side billing. Only a provider quota can. Use both.
- If your retry policy treats timeouts as free, your numbers will drift. Document the assumption.
- Ledgers grow fast. Rotate per run and keep about 14 days.
- If you make fewer than 100 calls a day, skip all of this. A spreadsheet is enough.
Should you build the ledger?
| Situation | Decision |
|---|---|
| Shared budget across many workers | Yes, with rid on every event |
| Single script, one run | No, log the usage line only |
| Hard spend ceiling required | Provider quota first, ledger second |
| Frequent transient timeouts | Yes, reserve must survive retries |
The checklist I reuse
- Replace the counter with an append-only log.
- Give every reservation an ID.
- Replay the log before you read the code.
- Count closes per ID. More than one is your bug.
- Assert the invariant in a test.
- Keep reservations open across retries.
- Reconcile against provider usage once per run.
- Checkpoint work so a pause is cheap.
The counter was never the problem. The missing owner was.
If you run this harness on a free tier, grep your ledger for multi-closed IDs first. That one line told me more than two hours of reading the retry code.
Top comments (0)