CLAUDE LABJP
DEADLINE — Today, August 2, is the last day to claim the one-time $100 promotional credit. It expires September 17, so claiming and spending have separate deadlinesGRANT — Applications for the AI for Science grant close today. Researchers working on rare genetic diseases can receive up to $50,000 in Claude credits over six monthsFORK — Claude Code now runs /fork as a background session, and the surrounding /resume and /background flows have been tidied up alongside itGUARD — Safeguards around WebSearch, subagent spawns, and Bash execution have been tightened. Worth checking whether any of your existing automation quietly stops workingCOUNTDOWN — Sonnet 5 stays at $2/$10 per Mtok through August 31. Standard pricing of $3/$15 per Mtok starts September 1, so revisit any estimate that spans the changeFABLE — Fable 5 is included in Max and Team Premium up to 50% of weekly usage limits, while Pro and Team Standard now reach it through metered creditsDEADLINE — Today, August 2, is the last day to claim the one-time $100 promotional credit. It expires September 17, so claiming and spending have separate deadlinesGRANT — Applications for the AI for Science grant close today. Researchers working on rare genetic diseases can receive up to $50,000 in Claude credits over six monthsFORK — Claude Code now runs /fork as a background session, and the surrounding /resume and /background flows have been tidied up alongside itGUARD — Safeguards around WebSearch, subagent spawns, and Bash execution have been tightened. Worth checking whether any of your existing automation quietly stops workingCOUNTDOWN — Sonnet 5 stays at $2/$10 per Mtok through August 31. Standard pricing of $3/$15 per Mtok starts September 1, so revisit any estimate that spans the changeFABLE — Fable 5 is included in Max and Team Premium up to 50% of weekly usage limits, while Pro and Team Standard now reach it through metered credits
Articles/Claude Code
Claude Code/2026-08-02Advanced

Resuming After a Budget Stop: What 36 Enumerated Stop Points Taught Me

A budget cap that halts a headless run is neither success nor failure. I enumerated every stop point, measured resume integrity and total spend, and ended up changing what the cap applies to.

Claude Code207Headless2Budget CapIdempotency2Automation41

Premium Article

One morning I opened the output of a job I had left running overnight, and the text stopped mid-sentence. The error log was empty. The exit code matched none of the patterns I had planned for.

The file was there, and it was a reasonable size. So the downstream step decided it had already been produced and moved on.

What had broken was not the job. It was an assumption inside my resume logic.

A budget stop is neither success nor failure

Claude Code's headless mode accepts --max-budget-usd, which terminates a run once cumulative cost crosses the threshold. Like the cap on concurrent subagents (currently 20), it exists to stop runaway work.

The part that is easy to miss is that this termination is a third kind of exit — neither success nor failure.

Exit typeWhat happenedDoes retrying help?
Normal exitAll steps finishedNot needed
Crash or exceptionAbnormal terminationYes, if the cause was transient
Budget cap stopA healthy step cut short by policyNo — the same cap stops at the same place

Crash-oriented designs assume you can detect an abnormal state and unwind it. A budget stop is the opposite: the process did exactly what policy told it to do. There is no anomaly recorded anywhere to unwind.

And the place it stops is decided by the moment cumulative cost crosses the threshold — that is, a position unrelated to any checkpoint I designed. It might land in the middle of writing a file, or immediately before the index update that would have made the write official.

I wanted to understand that "you don't know where it stops" property with numbers rather than intuition.

A rig for enumerating stop points

Sweeping --max-budget-usd across dozens of real runs would cost real money. For an indie developer's budget, that is not a reasonable way to learn something structural.

So I built a rig with the same shape in a Linux sandbox: a pipeline that charges cost units per step and calls os._exit(9) the instant the threshold is crossed. Exiting without unwinding is precisely what makes it resemble a budget stop.

#!/usr/bin/env python3
"""Pipeline used to enumerate stop points.
   mode=naive : overwrite in place; resume decides by file existence
   mode=safe  : write .part, fsync, os.replace; resume decides by index
"""
import json, os, sys, pathlib
 
ROOT, CAP, MODE = pathlib.Path(sys.argv[1]), float(sys.argv[2]), sys.argv[3]
ART, IDX = ROOT / "artifacts", ROOT / "index.json"
ITEMS = ["alpha", "bravo", "charlie", "delta"]
spent = 0.0
 
def charge(u):
    """Charge after each completed step; cross the cap and die without unwinding"""
    global spent
    spent += u
    with open(ROOT / "spend.log", "a") as f:
        f.write(f"{u}\n")
    if spent > CAP:
        os._exit(9)          # policy stop: not an exception, so no finally runs
 
def read_index():
    return json.loads(IDX.read_text())["items"] if IDX.exists() else []
 
def write_index(items):
    if MODE == "safe":
        t = IDX.with_suffix(".json.part")
        with open(t, "w") as f:
            json.dump({"items": items}, f)
            f.flush(); os.fsync(f.fileno())
        os.replace(t, IDX)   # atomic swap within the same filesystem
    else:
        IDX.write_text(json.dumps({"items": items}))
 
ART.mkdir(parents=True, exist_ok=True)
if MODE == "safe":
    for p in ART.glob("*.part"):
        p.unlink()           # sweep leftovers from the previous interruption
 
items = read_index()
done = {e["name"] for e in items}
 
for name in ITEMS:
    if MODE == "naive":
        if (ART / f"{name}.md").exists():
            continue         # naive rule: the file exists, so it must be done
    else:
        if name in done:
            continue         # the index is the single source of truth
 
    charge(1.0)              # plan
    body = f"# {name}\n" + "x" * 200_000 + "\nEND\n"
    charge(2.0)              # generate
 
    target = ART / f"{name}.md"
    path = target.with_suffix(".md.part") if MODE == "safe" else target
    with open(path, "w") as f:
        f.write(body[: len(body) // 2])
        f.flush(); os.fsync(f.fileno())
        charge(0.5)          # a stop point placed *inside* the write
        f.write(body[len(body) // 2 :])
        f.flush(); os.fsync(f.fileno())
    if MODE == "safe":
        os.replace(path, target)
    charge(0.5)
 
    items.append({"name": name, "bytes": len(body)})
    write_index(items)       # commit first, charge second — this ordering matters later
    charge(0.5)
 
print("COMPLETE")

Each item costs 1.0 + 2.0 + 0.5 + 0.5 + 0.5 = 4.5 units, so four items have an ideal total of 18.0. Sweeping the cap from 0.5 to 18.0 in 0.5 increments walks 36 distinct stop points.

The verifier is deliberately strict. Every indexed entry must exist on disk, match its recorded byte count, and end with a sentinel — and no .part file may remain.

#!/usr/bin/env python3
"""Strict post-resume verification: size match AND terminator, not just presence"""
import json, sys, pathlib
 
ROOT = pathlib.Path(sys.argv[1])
ART, IDX = ROOT / "artifacts", ROOT / "index.json"
EXPECT = ["alpha", "bravo", "charlie", "delta"]
bad = []
 
idx = {e["name"]: e["bytes"]
       for e in (json.loads(IDX.read_text())["items"] if IDX.exists() else [])}
 
for n in EXPECT:
    p = ART / f"{n}.md"
    if n not in idx:
        bad.append(f"missing-index:{n}"); continue
    if not p.exists():
        bad.append(f"missing-file:{n}"); continue
    data = p.read_bytes()
    if len(data) != idx[n]:
        bad.append(f"size-mismatch:{n}({len(data)}!={idx[n]})")
    elif not data.endswith(b"END\n"):
        bad.append(f"truncated:{n}")
 
for p in ART.glob("*.part"):
    bad.append(f"leftover-part:{p.name}")
 
print(("CORRUPT " + ",".join(bad)) if bad else "OK")

One caveat worth stating plainly: these numbers come from the rig, not from Claude Code's actual billing granularity, which is an implementation detail I cannot observe from outside. What transfers is the structure — a stop driven by internal charge points that do not line up with the boundaries you designed. That structure was what I wanted to examine.

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
A resume that treats file existence as completion silently accepted a 100,006-byte artifact that should have been 200,013 bytes (8 of 36 stop points ended in a corrupt final state)
The unbounded retry loop that appears the moment you add atomic writes, and the progress gate that cuts it off after 3.5 cost units instead of 42.0
Total spend is not monotonic in the cap. cap=8.0 cost 30.0; cap=8.5 cost 18.0. Scoping the cap to one resumable unit made waste 0% at every cap value
Secure payment via Stripe · Cancel anytime

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

Related Articles

Claude Code2026-06-27
Will It Stay Light When You Run It Unattended? Observing and Capping Claude Code's Long-Session Memory
How to keep long, unattended Claude Code sessions from slowly getting heavier — with a tiny ps-based RSS sampler, a rolling-baseline watchdog, and session segmentation, shown with working scripts and a before/after comparison.
Claude Code2026-07-25
Locking down Claude Code sandbox egress with strictAllowlist
Tightening automation egress with strictAllowlist in Claude Code v2.1.219, plus measured failure timings that tell a policy deny from DNS and real outages.
Claude Code2026-07-18
Branch with /fork, Delegate with /subtask — Two Commands That Traded Jobs
In Claude Code 2.1.212, /fork now clones your conversation into a background session, and in-session subagents moved to /subtask. Here is how I decide between them, and the budgets worth setting before you start branching freely.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →