CLAUDE LABJP
FORK — Claude Code 2.1.212 changes what /fork does: it copies your conversation into a new background session with its own row in claude agents, so you can keep working. The old in-session subagent is now /subtaskLIMITS — WebSearch calls are now capped at 200 per session by default, and subagent spawns get the same 200 ceiling, so a runaway search or delegation loop stops on its ownMCPBG — MCP tool calls running past two minutes now move to the background automatically, keeping the session usable. Tune the threshold with CLAUDE_CODE_MCP_AUTO_BACKGROUND_MSPLANFIX — Fixed plan mode auto-running file-modifying Bash commands such as touch and rm without a permission prompt or an SDK canUseTool callbackSONNET5 — Claude Sonnet 5 is running on introductory pricing of $2 per million input tokens and $10 per million output. After August 31 it moves to $3 and $15IPO — Bankers are reportedly lining up investor meetings for Anthropic ahead of a possible public listing as soon as OctoberFORK — Claude Code 2.1.212 changes what /fork does: it copies your conversation into a new background session with its own row in claude agents, so you can keep working. The old in-session subagent is now /subtaskLIMITS — WebSearch calls are now capped at 200 per session by default, and subagent spawns get the same 200 ceiling, so a runaway search or delegation loop stops on its ownMCPBG — MCP tool calls running past two minutes now move to the background automatically, keeping the session usable. Tune the threshold with CLAUDE_CODE_MCP_AUTO_BACKGROUND_MSPLANFIX — Fixed plan mode auto-running file-modifying Bash commands such as touch and rm without a permission prompt or an SDK canUseTool callbackSONNET5 — Claude Sonnet 5 is running on introductory pricing of $2 per million input tokens and $10 per million output. After August 31 it moves to $3 and $15IPO — Bankers are reportedly lining up investor meetings for Anthropic ahead of a possible public listing as soon as October
Articles/API & SDK
API & SDK/2026-05-02Intermediate

Cancelling Claude API Streams the Right Way: AbortController, Token Billing, and Connection Hygiene

I measured 100 cancelled Claude API streams to find out what actually gets billed. The drift numbers, the $18.70/month a missing close handler costs you, the proxy buffering trap, and a ledger that never loses a usage event — Node.js and Python.

claude-api81streaming21abortcontrollercost-optimization28node-jspython22

Premium Article

The first question I got from a teammate after we shipped a streaming chat UI was: "When the user hits the Stop button mid-response, do we still get billed for what's already generated?" The Anthropic SDK ships a perfectly fine cancellation mechanism, but reading the docs end to end doesn't quite answer "so what shows up on the invoice?" That part you only learn by sending requests, killing them, and inspecting the usage events.

Here's a focused guide to cancelling Claude API streams cleanly with AbortController, what the billing actually looks like when you do, and the connection-pool gotcha that took down our chat endpoint once. Examples are in TypeScript (Node.js) and Python (asyncio), tested against the latest Anthropic SDKs and Claude Sonnet 4.6.

As an indie developer I shipped that chat feature into an iOS app I maintain alone, without ever verifying what a cancelled request costs. What eventually hurt was not the invoice. It was the alert that woke me at 2am when the connection pool ran dry.

Bottom line: what you generate, you pay for

The single sentence to internalize:

  • Input tokens (the prompt) are billed in full the moment the server starts processing — cancelling does not refund them.
  • Output tokens are billed for what was actually generated. If you cut the stream after 12 tokens, you pay for 12.
  • A few tokens may already be in flight on the server when you abort, so expect a small variance (typically 5 to 30 tokens past the visual cut point).

So "I cancelled, therefore zero" is wrong. The right model is "I paid through the cut point, plus a little drift." Anthropic surfaces incremental token counts in the message_delta event's usage.output_tokens field. Capture the most recent value before the abort fires and your accounting will be accurate.

In practice on Sonnet 4.6, an aggressive user who hits Stop right as the first character renders will see the full prompt billed and roughly 5–30 output tokens — pennies for almost any production workload. The cost angle isn't the thing to lose sleep over. The connection-pool angle is. More on that below.

Node.js (TypeScript): wire up AbortController properly

The Anthropic SDK's messages.stream() accepts an AbortSignal. The cleanest pattern in Express is to bind a controller to the request lifecycle so a client disconnect tears the upstream stream down too.

import Anthropic from "@anthropic-ai/sdk";
import express from "express";
 
const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY! });
const app = express();
 
app.post("/api/chat/stream", express.json(), async (req, res) => {
  // Tear down the upstream call when the browser disconnects
  const controller = new AbortController();
  req.on("close", () => controller.abort());
 
  res.setHeader("Content-Type", "text/event-stream");
  res.setHeader("Cache-Control", "no-cache, no-transform");
  res.setHeader("Connection", "keep-alive");
 
  let outputTokens = 0;
  try {
    const stream = await client.messages.stream(
      {
        model: "claude-sonnet-4-6",
        max_tokens: 1024,
        messages: req.body.messages,
      },
      { signal: controller.signal },
    );
 
    for await (const event of stream) {
      if (event.type === "content_block_delta" && event.delta.type === "text_delta") {
        res.write(`data: ${JSON.stringify({ text: event.delta.text })}\n\n`);
      } else if (event.type === "message_delta") {
        // Keep overwriting — the last value before abort is what you pay for
        outputTokens = event.usage.output_tokens;
      }
    }
 
    res.write(`data: [DONE]\n\n`);
    res.end();
  } catch (err: any) {
    // AbortError is the success path for "user clicked Stop"
    if (err.name === "AbortError" || controller.signal.aborted) {
      console.log(`Stream cancelled. Output tokens billed: ${outputTokens}`);
      return; // res is already closed
    }
    console.error(err);
    res.write(`data: ${JSON.stringify({ error: "stream_failed" })}\n\n`);
    res.end();
  } finally {
    await recordUsage({ outputTokens, cancelled: controller.signal.aborted });
  }
});
 
async function recordUsage(_: { outputTokens: number; cancelled: boolean }) {
  // Persist to your metrics pipeline (Postgres, Datadog, whatever)
}

Three details matter here. First, req.on("close", () => controller.abort()) — without this line the upstream stream keeps consuming tokens after the user has closed the tab. Second, capture event.usage.output_tokens on every message_delta; the last value you saw before the abort is the one you'll be billed for. Third, treat AbortError as a success path, not an error. Logging it at error level is the fastest way to make your alerting useless.

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
Real drift numbers from 100 measured cancellations (12 mean, 27 p95, 34 max output tokens) so you know exactly what a Stop button costs
The 64x gap between drift and a missing close handler: roughly $0.29 vs $18.70 a month, and the mechanics behind it
A production ledger that composes user cancel, timeout, and SIGTERM into one AbortSignal while still recovering which one fired
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

API & SDK2026-05-06
Claude API × Python in Practice: Building an AI Assistant with Tool Calling and Streaming
A practical guide to combining Claude API's Tool Use and Streaming in Python. Build a working AI assistant with real tool execution, complete source code included, plus a breakdown of the tricky parts that trip up most developers.
API & SDK2026-03-24
Claude API Token Counting Guide — How to Estimate Token Usage and Optimize Costs Before Sending Requests
Learn how to use the Claude API Token Counting endpoint to estimate token usage before sending messages. Covers cost management, context window optimization, and production implementation patterns.
API & SDK2026-07-13
Coalescing Concurrent Claude API Calls: Single-Flight Against Duplicate Inference and Cache Stampede
A design for collapsing identical prompts that fire at the same instant into a single upstream Claude call, using single-flight (request coalescing). In-process and distributed implementations, jittered retries, and negative caching, with measured results.
📚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 →