CLAUDE LABJP
BUDGET — You can now cap what a Claude Managed Agents session spends. When it hits the cap, the session stops issuing new model requests and returns a budget_reached stop reasonRESUME — Change or clear the budget and the session picks up again. Deployments take the same setting, but it applies per session they start, not to the deployment as a wholeGEO — A new inference_geo field controls where inference runs. Set it inside the model object when creating an agent, or override it for a single session. It takes us or globalSKILLS — When a Managed Agents session mounts a GitHub repository, any skills sitting in its root .claude/skills directory are discovered automatically at session startTRADEOFF — Convenience and context cost sit on the same scale. Every extra skill you load also shows up in what /skill-doctor charges you each turnCLI — Claude Code has not shipped a confirmed release since v2.1.263 on September 6. Version numbers skip, so check the official changelog against CHANGELOG.md before quoting oneBUDGET — You can now cap what a Claude Managed Agents session spends. When it hits the cap, the session stops issuing new model requests and returns a budget_reached stop reasonRESUME — Change or clear the budget and the session picks up again. Deployments take the same setting, but it applies per session they start, not to the deployment as a wholeGEO — A new inference_geo field controls where inference runs. Set it inside the model object when creating an agent, or override it for a single session. It takes us or globalSKILLS — When a Managed Agents session mounts a GitHub repository, any skills sitting in its root .claude/skills directory are discovered automatically at session startTRADEOFF — Convenience and context cost sit on the same scale. Every extra skill you load also shows up in what /skill-doctor charges you each turnCLI — Claude Code has not shipped a confirmed release since v2.1.263 on September 6. Version numbers skip, so check the official changelog against CHANGELOG.md before quoting one
Articles/API & SDK
API & SDK/2026-09-08Intermediate

Writing 25 as your session budget caps it at twenty-five cents

Managed Agents session budgets are expressed in minor units as a string. Here is how I mixed up the unit, hit budget_reached in minutes, and what raising versus clearing a budget actually does.

managed-agents6session4budget3cost3sdk5

I had left a verification session running overnight, and that morning was the first time I attached a spending ceiling to it. I meant to let it run up to twenty-five dollars, so I wrote "25" into amount. The session had stopped after a handful of turns, with nothing in the log but a stop reason of budget_reached.

I suspected the model. Then the environment. Only when I finally opened the SDK type definitions did it click. The amount is counted in minor units. My "25" was never twenty-five dollars — it was twenty-five cents.

An amount is not a number; it is a string counted in the currency's smallest unit. Keeping that one line in mind has been enough to stop me from repeating it.

Budgets travel as integer strings in minor units

A session budget is a small structure called BetaManagedAgentsBudgetLimit. Reading the type definitions in @anthropic-ai/sdk 0.124.0, it holds exactly two things.

FieldTypeMeaning
type'limit'Fixed value
max_list_cost{ amount, currency }Where it stops

The answer about units sits right in the doc comment on amount. It takes an integer in minor units, as a decimal string with no leading zeros. "2500" is $25.00 and "50" is fifty cents. The comment even explains the choice of a string: so that no float rounding is ever applied.

currency is an uppercase ISO-4217 code, and USD is the only one supported right now.

You can confirm all of this on your own machine in a couple of minutes, and I would rather you did that than take my word for it.

# No API key needed. This only reads the shipped type definitions.
mkdir -p ~/probe && cd ~/probe && npm init -y >/dev/null
npm install @anthropic-ai/sdk
grep -A8 "interface BetaMonetaryAmount" \
  node_modules/@anthropic-ai/sdk/resources/beta/beta.d.ts
# Expected: the amount comment mentions "minor units" and '"2500" is $25.00'

The package itself lives at @anthropic-ai/sdk on npm.

Creating a session with a budget attached

The minimum shape is short. The part that matters to me is wrapping the dollars-to-minor-units conversion in a function, so that a hand-typed "25" never gets the chance to appear again.

import Anthropic from "@anthropic-ai/sdk";
 
const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
 
// Dollars in, minor-unit string out. One small gate against the mistake I made.
function usd(dollars) {
  const cents = Math.round(dollars * 100);
  if (!Number.isFinite(cents) || cents < 0) {
    throw new Error(`Invalid budget: ${dollars}`);
  }
  return { amount: String(cents), currency: "USD" };
}
 
const session = await client.beta.sessions.create({
  agent: "agent_xxxxxxxx",
  environment_id: "env_xxxxxxxx",
  budget: { type: "limit", max_list_cost: usd(25) }, // → amount: "2500"
  betas: ["managed-agents-2026-04-01"],
});
 
console.log(session.budget);
// Expected output:
// { type: 'limit', max_list_cost: { amount: '2500', currency: 'USD' } }

Print session.budget once after creation. If amount reads "2500", you got what you intended. If it still reads "25", the session is running under a ceiling one hundred times lower than you think. A single log line here would have saved me the whole detour.

If you want different ceilings per session on top of one shared agent, the shape of that arrangement is covered in running one shared agent definition with per-session overrides.

Two ways to learn that it stopped

A session that reaches its ceiling does not start new model requests; it goes idle. You can find out through either of two channels.

The first is the stop_reason on a session.status_idle event. The type definitions list four possible values, and budget_reached is one of them.

stop_reasonStateYour move
end_turnTurn finishedSend the next message
requires_actionAwaiting confirmationReturn a tool confirmation
retries_exhaustedRetries used upHand it to a person
budget_reachedCeiling reachedRaise it or clear it
const stream = await client.beta.sessions.events.stream({
  session_id: session.id,
  betas: ["managed-agents-2026-04-01"],
});
 
for await (const event of stream) {
  if (event.type !== "session.status_idle") continue;
 
  if (event.stop_reason.type === "budget_reached") {
    const s = await client.beta.sessions.retrieve(session.id, {
      betas: ["managed-agents-2026-04-01"],
    });
    // list_cost is in minor units too. Divide by 100 only for display.
    const spent = Number(s.usage?.list_cost?.amount ?? 0) / 100;
    console.log(`Budget reached: spent $${spent.toFixed(2)}`);
    break;
  }
}

The second channel is a webhook. There is an event type named session.budget_reached, and the payload is deliberately plain: a session ID, an organization ID, and a workspace ID. If you want the number, fetch the session after the webhook arrives.

For anything running unattended, what has worked for me is to let the webhook wake a queue and leave the actual decision for hours when a person is looking. Raising a ceiling automatically at three in the morning quietly cancels the reason the ceiling existed.

The distinction between the two channels matters more than it first appears. The stream tells you the shape of the stop, because stop_reason is a tagged union and you can branch on it directly. The webhook tells you only that something happened, which is the right amount of information for a process that was not watching. As an indie developer running scheduled jobs against a handful of small products, I lean on the webhook for the alerting path and reserve the stream for the sessions I am actively debugging. Holding a stream open all night to catch an event that may never arrive has never paid for itself in my setup.

One more detail worth reading carefully: list_cost on the usage snapshot is optional. A session that has not yet been priced returns nothing there, so treat a missing value as unknown rather than as zero. Printing $0.00 for a session that actually spent something is the kind of quiet wrongness that survives for months.

Raising it, versus clearing it altogether

There are two ways back to a running session. The obvious one is to lift the ceiling.

await client.beta.sessions.update(session.id, {
  budget: { type: "limit", max_list_cost: usd(50) }, // $25 → $50
  betas: ["managed-agents-2026-04-01"],
});

Here the type definitions carry a caveat I had not anticipated. budget_reached is returned not only when the money runs out, but also when the session's usage includes a model that has no list price — something a budget cannot measure. In that case, a request to raise the ceiling is rejected. Adding headroom to something unmeasurable buys nothing, so if you want to continue, you remove the budget instead.

// null is accepted on update only; the create parameter does not take null.
await client.beta.sessions.update(session.id, {
  budget: null,
  betas: ["managed-agents-2026-04-01"],
});

The budget field on create and the one on update have subtly different types. Create takes a value; update also accepts null. Trying to express "remove this" through the create parameter will not go through, so clearing always belongs on the update path.

A budget on a deployment is not a total

Deployments accept the same budget structure, and this is the spot I misread first. I assumed it meant twenty-five dollars for the whole deployment.

It applies to each session started from that deployment, individually. A deployment carrying a $25 budget across twenty sessions is enforcing that ceiling twenty separate times. If you want a real cap on the sum, you still need something of your own that tallies spend and stops the work — the reasoning behind that is laid out in holding a hard spend ceiling in production with a circuit breaker, which is where I would start if the total is what keeps you up at night.

For today, print budget.max_list_cost.amount on the sessions you already have running. If it is short by two digits, that is the cheapest bug you will find all week.

Thank you for reading this far. A unit that silently divides your runway by a hundred is simple enough once you know it, and nearly invisible until you do. If this spares even one person that particular morning, it was worth writing down.

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 $15 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

API & SDK2026-07-09
The 200K-Token Cliff That Doubled My Nightly Bill — A count_tokens Preflight to Avoid Long-Context Pricing
Sonnet 5's native 1M context switches the entire request to long-context pricing the moment input crosses 200K tokens. Here's how I caught that silent cost cliff before sending, using the billing-exempt count_tokens API, with working Python and TypeScript code.
API & SDK2026-06-14
Wiring Claude's Dreaming Into Memory Hygiene for Long-Running Agents
Managed Agents' Dreaming reviews past sessions and rewrites memory as a self-improvement loop. We unpack the published Harvey and Wisedocs numbers, build a complete self-hosted consolidation loop with the Anthropic SDK, and cover the production pitfalls.
API & SDK2026-06-13
Managed Agents Adds Scheduled Deploys, a Vault, and Session Webhooks — Deciding What Leaves My Cron Setup
Scheduled deploys, vault credentials, and session-thread webhooks just landed in Claude Managed Agents. How I triaged my self-hosted cron jobs: what moves, what stays, and why idempotency decides.
📚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