◉CLAUDE LABEN
●2.1.301 — vim の % モーション、VS Code パネルでの LaTeX 数式表示、使用量上限待ちのリトライ監視の上限が入りました(10/15)●2.1.299 — 200K トークン超で約54分放置した会話はアイドル圧縮。MCP の許可ルールは名指ししたサーバーだけに一致します●SONNET 4.5 — Claude API での提供終了は 11/30、残り45日。移行先は Sonnet 5.5●WINDOWS — 日本語の環境でだけフックが黙って壊れる報告があり、文字コードの確認が呼びかけられています●FAST MODE — Opus 5.5 の Fast mode がリサーチプレビューを終え、ベータヘッダーが不要になりました(10/15)●NEW — Cowork に渡したフォルダの先頭に、十行の約束ファイルを置いてみました●2.1.301 — vim の % モーション、VS Code パネルでの LaTeX 数式表示、使用量上限待ちのリトライ監視の上限が入りました(10/15)●2.1.299 — 200K トークン超で約54分放置した会話はアイドル圧縮。MCP の許可ルールは名指ししたサーバーだけに一致します●SONNET 4.5 — Claude API での提供終了は 11/30、残り45日。移行先は Sonnet 5.5●WINDOWS — 日本語の環境でだけフックが黙って壊れる報告があり、文字コードの確認が呼びかけられています●FAST MODE — Opus 5.5 の Fast mode がリサーチプレビューを終え、ベータヘッダーが不要になりました(10/15)●NEW — Cowork に渡したフォルダの先頭に、十行の約束ファイルを置いてみました
記事一覧/API & SDK
⬡ API & SDK/2026-05-02中級

Claude API のストリーミングを途中で止める — AbortController の正しい使い方と請求の仕組み

Claude API のストリーミングを AbortController で止めたとき、実際に請求される出力トークンを100回計測しました。中断ドリフトの実測値、close 未配線が生む月18.7ドルの差、プロキシのバッファリング問題までを Node.js と Python の実装つきで扱います。

claude-api83streaming16abortcontrollercost-optimization28node-jspython22

✦ プレミアム記事

「ユーザーが『止めて』ボタンを押した瞬間、Claude API のレスポンスを止めたい。でも、止めた分のトークンって課金されるの?」 — チャット UI を本番に出した直後、私が同僚から最初に受けた質問です。Anthropic SDK にはちゃんとキャンセル機構が用意されているのですが、ドキュメントを順に読むだけだと「で、結局いくら請求されるのか」までは見えにくいところがあります。

ここではストリーミング応答を AbortController で途中停止する実装と、キャンセル時の出力トークンが実際にどう課金されるかを、Node.js(TypeScript)と Python の両方で整理します。検証したのは Anthropic SDK の最新版(v0.30 系)と Claude Sonnet 4.6 の組み合わせです。

個人開発で運用している iOS アプリに相談チャットを載せたとき、私はこの「止めたときの請求」を確かめないまま本番に出しました。結果として痛い目を見たのは請求額ではなく、深夜に鳴ったアラートのほうでした。その顛末も含めて書きます。

結論を先に:キャンセルしても、生成済みの分は課金される

最初に押さえておきたいのはここだけです。

  • 入力トークン(プロンプト分)は、キャンセルしても全額課金されます。サーバーがリクエストを受け取って処理を開始した時点で確定します。
  • 出力トークンは、生成された分だけ課金されます。ストリームを途中で切れば、その地点までのトークンが請求対象になります。
  • AbortController で接続を切っても、サーバー側で「次の数トークン」が生成済みになっている可能性があります。請求書には数トークンの誤差が乗ることがあります。

つまり「止めたから 0 円」ではなく、「止めたところまでは払う」が正しい理解です。Anthropic の usage レスポンス(message_delta イベントの usage.output_tokens)は、キャンセルされてもストリーム内で送られていた分は返ってきます。後述するコードで、最後に届いた usage を必ず保存しておくと、コスト集計が正確になります。

実際の数字感としては、Sonnet 4.6 のチャットで 1 文字目が出始めた段階で止めると、入力トークン全額 + 出力 5〜30 トークン程度が請求されることが多いです。ヘビーユーザー向けに「キャンセルボタン」を提供するときは、UX 上のメリットの方が大きいので、課金面はあまり気にしなくて構いません。むしろ気をつけたいのは、サーバー側の同時接続が積み上がるパターンです(後述)。

Node.js(TypeScript)での実装

Anthropic SDK の messages.stream() は、AbortSignal を受け付けます。Express のリクエストハンドラから AbortController を渡すパターンが一番素直です。

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) => {
  // クライアントが切断したらサーバー側でも止める
  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") {
        // 最後に届いた usage を保存(キャンセル時もここまでの分は届く)
        outputTokens = event.usage.output_tokens;
      }
    }
 
    res.write(`data: [DONE]\n\n`);
    res.end();
  } catch (err: any) {
    // AbortError は正常系として扱う(ユーザーが止めただけ)
    if (err.name === "AbortError" || controller.signal.aborted) {
      console.log(`Stream cancelled. Output tokens billed so far: ${outputTokens}`);
      return; // res は既に閉じている
    }
    console.error(err);
    res.write(`data: ${JSON.stringify({ error: "stream_failed" })}\n\n`);
    res.end();
  } finally {
    // 必要なら usage を DB / 集計サービスに送る
    await recordUsage({ outputTokens, cancelled: controller.signal.aborted });
  }
});
 
async function recordUsage(_: { outputTokens: number; cancelled: boolean }) {
  // ここで Postgres / DataDog 等に記録
}

ポイントは三つあります。一つ目は req.on("close", () => controller.abort()) の一行。これがないと、ユーザーがブラウザを閉じてもサーバー側はストリームを最後まで読み続けてしまい、無駄なトークンを消費します。二つ目は event.type === "message_delta" のタイミングで usage.output_tokens を毎回上書きしておくこと。三つ目は AbortError を握り潰すこと。これは「エラー」ではなく「ユーザーがキャンセルした」という正常系のシグナルなので、ログに error レベルで出すと運用ノイズになります。

✦

ここまでお読みいただきありがとうございます。

この記事の続きを読む

この先には、実装コードやベンチマーク結果など、実務でお役に立てる内容をご用意しています。このサイトは広告を掲載しておらず、サーバーや開発にかかる費用はメンバーの皆様のご支援で成り立っています。もしお役に立てていましたら、ご支援いただけますと大変ありがたいです。

この記事で得られること
✦キャンセル時に余分に生成される出力トークン(平均12・p95 27・最大34トークン)を100回計測した実データで、課金の実像を把握できます
✦close ハンドラを配線し忘れたときの月額差(約0.29ドル 対 約18.7ドル・64倍)と、その差が生まれる仕組みを追えます
✦ユーザー中断・タイムアウト・プロセス停止を AbortSignal.any で束ねつつ停止理由を復元し、usage を取りこぼさない計測レイヤーの実装を持ち帰れます
Stripe による安全な決済 · いつでもキャンセル可能
✦

この記事を購入する

この先の内容をすべてお読みいただけます。一度のご購入で、いつでも何度でもアクセスできます。このサイトは広告を掲載しておらず、皆さまのご支援がサーバー費用などの運営を支えています。

または
メンバーシップなら全記事が読み放題 →
シェア

お読みいただきありがとうございます

Claude Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • ✦コピー&ペーストで使える実装コード付き
  • ✦毎日新しい上級ガイドを追加
  • ✦¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

関連記事

⬡ API & SDK2026-09-07
キャッシュ読み取りが 75% 安くなっても、請求の下がり方は構成しだいで 10 倍違います
Fable 5.1 と Mythos 5.1 でキャッシュ読み取りの倍率が 0.1 から 0.025 へ変わりました。自分のトークン内訳から削減幅を先に見積もる手順と、倍率を定数で持つコードの直し方をまとめます。
⬡ API & SDK2026-06-12
Claude API の Code Execution ツールに月次の売上CSV集計を任せる — Files API 連携から課金の落とし穴まで
月末に形式の違う売上CSVを突き合わせる作業を、Claude API の Code Execution ツールに任せた実装をまとめます。Files API 連携、コンテナ再利用、5分最小課金やファイルプリロード課金などの注意点を実例ベースで扱います。
⬡ API & SDK2026-05-25
二段構えのモデル構成 — Haiku 4.5 オーケストレーター × Opus 4.6 ワーカーで保つコストと品質の均衡
オーケストレーションをHaiku 4.5に、専門タスクをOpus 4.6に任せる二段構えの構成を、公表単価に当てた月次試算つきで書き残します。ワーカーに任せる5つの判定基準、ストリーミングの使い分け、詰まった落とし穴、そして来なかった値上げに備えて設計を動かしてしまった失敗まで。