CLAUDE LABEN
FORK — Claude Code 2.1.212で/forkの挙動が変わりました。会話を新しいバックグラウンドセッションへ複製し、作業を続けたまま並走できます。従来のセッション内サブエージェントは/subtaskに移りましたLIMITS — WebSearchの呼び出しがセッション単位で既定200回に制限されました。サブエージェントの起動も既定200回が上限で、暴走した検索・委譲のループを止められますMCPBG — 2分を超えるMCPツール呼び出しは自動的にバックグラウンドへ移り、セッションが固まらなくなりました。しきい値はCLAUDE_CODE_MCP_AUTO_BACKGROUND_MSで調整できますPLANFIX — プランモードがtouchやrmといったファイルを変更するBashコマンドを、許可プロンプトもcanUseToolコールバックも通さずに実行してしまう不具合が修正されましたSONNET5 — Claude Sonnet 5は導入価格として入力100万トークンあたり2ドル、出力10ドルで提供中です。8月31日を過ぎると3ドルと15ドルに戻りますIPO — Anthropicが早ければ10月の株式公開を視野に、引受銀行が投資家との面談を組み始めたと報じられていますFORK — Claude Code 2.1.212で/forkの挙動が変わりました。会話を新しいバックグラウンドセッションへ複製し、作業を続けたまま並走できます。従来のセッション内サブエージェントは/subtaskに移りましたLIMITS — WebSearchの呼び出しがセッション単位で既定200回に制限されました。サブエージェントの起動も既定200回が上限で、暴走した検索・委譲のループを止められますMCPBG — 2分を超えるMCPツール呼び出しは自動的にバックグラウンドへ移り、セッションが固まらなくなりました。しきい値はCLAUDE_CODE_MCP_AUTO_BACKGROUND_MSで調整できますPLANFIX — プランモードがtouchやrmといったファイルを変更するBashコマンドを、許可プロンプトもcanUseToolコールバックも通さずに実行してしまう不具合が修正されましたSONNET5 — Claude Sonnet 5は導入価格として入力100万トークンあたり2ドル、出力10ドルで提供中です。8月31日を過ぎると3ドルと15ドルに戻りますIPO — Anthropicが早ければ10月の株式公開を視野に、引受銀行が投資家との面談を組み始めたと報じられています
記事一覧/API & SDK
API & SDK/2026-05-04中級

Claude API の stop_sequences が効かないときに確認すべき5つのポイント

Claude API の stop_sequences パラメータが期待通りに止まらない問題の原因を、トークン境界・空白・Tool Use・ストリーミングの観点から実例コード付きで切り分ける手順をまとめました。

api38stop-sequencestroubleshooting61tokenizationstreaming16

stop_sequences"\n\n" を渡したのに、出力が止まる気配がない」— Claude API を使い込んでいると、一度はぶつかる現象ではないでしょうか。私も RAG 風のフォーマット制御を組んでいたときに、まったく同じ問題で半日を溶かしました。

結論から言えば、stop_sequences が効かないように見える原因のほとんどは「Claude が生成した文字列とあなたが指定した文字列が、トークンレベルで一致していない」ことに起因します。ここでは現場で実際に遭遇する 5 つの典型パターンを切り分け、それぞれに具体的な対処コードを示します。

まず把握しておくべき stop_sequences の仕様

Claude API の stop_sequences は、生成中のテキストにバイト単位で完全一致する文字列が出現したタイミングで生成を停止させるパラメータです。Anthropic の公式ドキュメントでは「最大 4 つまで指定可能」「stop_reason"stop_sequence" になる」と書かれていますが、現場で詰まるのはこのあたりではありません。

実装上、押さえておくべきポイントは次の 3 つです。

  • 検査対象は「Claude が今まさに出力している文字列バッファ」であり、プロンプト側のテキストではありません
  • マッチした停止文字列は出力に含まれません(Tool Use の場合を除く)
  • stop_reason には "end_turn" / "max_tokens" / "stop_sequence" / "tool_use" のいずれかが入ります。停止理由を必ずログに残しておくと、原因特定が一気に楽になります
# stop_reason は必ずログに残す
import anthropic
 
client = anthropic.Anthropic()
response = client.messages.create(
    model="claude-sonnet-4-5",
    max_tokens=1024,
    stop_sequences=["\n\nHuman:"],
    messages=[{"role": "user", "content": "5から1までカウントダウンして"}],
)
 
print(f"stop_reason: {response.stop_reason}")
print(f"stop_sequence: {response.stop_sequence}")  # マッチした文字列(マッチしなかったら None)
print(f"content: {response.content[0].text}")

stop_reason"end_turn""max_tokens" になっていたら、そもそも停止文字列が出力に現れていません。一方、"stop_sequence" なのに止まる位置が思った通りでない場合は、後述する「空白の取り扱い」や「トークン境界」の問題を疑います。

原因 1: 出力にそもそも停止文字列が出現していない

最も多い勘違いがこれです。stop_sequences は「Claude が出力しないでほしい文字列」を指定する機能ではなく、「Claude が出力したらそこで止める文字列」を指定する機能です。

たとえば「JSON だけ返してほしいから "```" で止めよう」と考えても、Claude がコードブロック記法を使わずに JSON を直接返してきたら、停止文字列は永遠に現れません。stop_reason"end_turn" で返ってくるはずです。

対処は、プロンプト側で停止文字列を必ず出力するように指示することです。

# ❌ 望ましくない: 停止文字列が出るかどうかは Claude 任せ
prompt = "ユーザー情報を JSON で返してください"
stop_sequences = ["```"]
 
# ✅ 推奨: 停止文字列を必ず出力するフォーマットを指示する
prompt = """ユーザー情報を以下の形式で返してください。
=== JSON START ===
{...JSON...}
=== JSON END ===
JSON 出力後は必ず === JSON END === を出力してください。"""
stop_sequences = ["=== JSON END ==="]

「Claude が必ず出すマーカー」を自分で設計するのが、現場で安定する書き方です。

原因 2: 空白・改行の数が一致していない

\n\n で止めたいのに止まらない」という相談をよく受けます。原因は、Claude が出力しているのが \n\n ではなく \n 1 つだけだったり、逆に \n\n\n だったりするケースです。

トークナイザの観点から言うと、Claude は連続する空白や改行を、状況に応じて異なるトークン列にエンコードします。stop_sequencesバイト単位の一致を見るので、\n の数が 1 つでも違うとマッチしません。

切り分け方は、まず stop_sequences を外して 1 回生成し、出力を repr() で確認することです。

# 切り分け用: まずは stop_sequences なしで生成して実際の出力を見る
response = client.messages.create(
    model="claude-sonnet-4-5",
    max_tokens=512,
    messages=[{"role": "user", "content": "5から1までカウントダウンして"}],
)
print(repr(response.content[0].text))
# 出力例: '5\n4\n3\n2\n1' ← \n\n ではなく \n だった

期待していた \n\n ではなく \n だけなら、stop_sequences=["\n"] に変えるか、プロンプトで「各項目の後に空行を入れてください」と明示します。

原因 3: トークン境界をまたぐ停止文字列

これが私自身ハマった一番厄介なケースです。Claude のトークナイザは、たとえば "###" のような文字列を、状況に応じて 1 トークンにも 2〜3 トークンにも分割します。

stop_sequences のマッチング自体はバイト単位で行われるので、原理的にはトークン境界の影響は受けないはずです。しかし実装上、ストリーミング応答ではトークン単位で配信されるため、停止文字列が複数トークンに分割されてストリームを越えると、消費側のロジックが不安定になることがあります。

特に、SSE で受信したテキストを逐次表示しつつ手元でも stop_sequences 相当の判定をしているコードでは、トークン境界をまたいだ停止文字列を取りこぼします。

対処は次の 2 つです。

# ✅ サーバー側の stop_sequences に任せ、クライアント側で再判定しない
with client.messages.stream(
    model="claude-sonnet-4-5",
    max_tokens=1024,
    stop_sequences=["=== END ==="],
    messages=[{"role": "user", "content": "..."}],
) as stream:
    for text in stream.text_stream:
        print(text, end="", flush=True)
 
    # サーバーが停止判定を終えた後の最終メッセージで stop_reason を確認
    final = stream.get_final_message()
    print(f"\nstop_reason: {final.stop_reason}")
# ✅ クライアント側でも判定したいなら、複数トークンを跨ぐバッファを持つ
buffer = ""
STOP = "=== END ==="
 
with client.messages.stream(...) as stream:
    for text in stream.text_stream:
        buffer += text
        if STOP in buffer:
            # 停止文字列の手前までを最終出力として扱う
            final_text = buffer.split(STOP)[0]
            break

クライアント側の二重判定はバグの温床になりやすいので、可能な限りサーバー側の stop_sequences に任せるのが安全です。

原因 4: Tool Use モードでは挙動が変わる

tools パラメータを併用したリクエストでは、stop_sequences の優先順位が変わります。具体的には、Claude がツール呼び出しを開始した時点で stop_reason"tool_use" になり、stop_sequences の判定はそれ以降の出力に対してのみ働きます。

# Tool Use と stop_sequences を併用するときの挙動
response = client.messages.create(
    model="claude-sonnet-4-5",
    max_tokens=2048,
    tools=[{"name": "get_weather", "input_schema": {...}, "description": "天気を取得"}],
    stop_sequences=["=== END ==="],
    messages=[{"role": "user", "content": "東京の天気を教えて、最後に === END === と書いてください"}],
)
 
# Claude がツールを呼び出すと stop_reason = "tool_use" で一時停止する
# stop_sequences の判定はこのターンでは行われない
print(response.stop_reason)  # "tool_use"

Tool Use 経由で複数ターンの会話を構築している場合、最終ターンの平文応答で初めて stop_sequences が機能します。「ツール呼び出しを stop_sequences で止めたい」という設計は成立しないので、その用途には tool_choice パラメータを使ってください。

私の経験では、エージェント実装で「途中で止まらない」という相談の半分以上は、Tool Use と stop_sequences の役割分担を取り違えているケースでした。

原因 5: extended thinking が有効になっている

Claude の Extended Thinking(拡張思考)モードが有効な場合、stop_sequences思考ブロックには適用されません。停止判定は最終的な回答テキストにのみ働きます。

これは公式ドキュメントにも明記されていますが、見落とされがちなポイントです。Extended Thinking を有効にして「JSON だけ返してほしいから } で止めよう」とした場合、思考ブロック内に } がいくら現れても止まりません。

response = client.messages.create(
    model="claude-sonnet-4-5",
    max_tokens=4096,
    thinking={"type": "enabled", "budget_tokens": 2000},
    stop_sequences=["}"],  # ← 思考ブロック内の } では止まらない
    messages=[{"role": "user", "content": "計算結果を JSON で返して"}],
)
 
# response.content は [thinking_block, text_block] のリスト
# stop_sequences の判定は text_block にのみ適用される
for block in response.content:
    print(block.type, "->", block.text[:50] if hasattr(block, "text") else block.thinking[:50])

Extended Thinking を併用する場合は、回答セクションのフォーマットを明示し、その中に専用の停止マーカーを設計するのが確実です。

切り分けチェックリスト

ここまでの内容を、現場で詰まったときに上から順に試せるチェックリストにまとめました。

  • stop_reason の値を確認する(end_turn なら原因 1、stop_sequence ならパターン特定が必要)
  • stop_sequences を外して repr() で実際の出力を確認する
  • 改行・空白の数が想定と一致しているか確認する
  • ストリーミング使用時はクライアント側で二重判定していないか確認する
  • tools パラメータを使っているかどうか確認する
  • Extended Thinking が有効になっていないか確認する

このチェックリストで原因が絞れない場合は、停止文字列の設計そのものを「Claude が確実に出力するマーカー方式」に切り替えるのが、最も投資対効果の高い解決策です。

設計を変えるという選択肢

最後に、トラブルシューティングを超えた話を 1 つだけ。stop_sequences でフォーマットを制御するのは、本質的には「文字列マッチングによる制御」であり、Claude の自然言語生成の柔軟性とは相性が悪い面があります。

私はここ半年ほど、フォーマット制御が必要な用途では stop_sequences よりも次の 2 つを優先するようになりました。

  • Tool Use(function calling): 構造化出力が必要なら、JSON Schema 付きのツール定義を渡してツール呼び出し形式で受け取る
  • Constrained sampling 系の機能: Anthropic SDK の response_format 系オプションが提供される場合は積極的に使う

トラブルシューティングで時間を溶かす前に、「そもそも stop_sequences が最適解か?」を一度立ち止まって考える価値はあります。

個人開発で複数のアプリと Dolice Labs のサイト群を並行して回している身としては、停止文字列の不一致のような細部に半日を溶かすより、最初から壊れにくい設計に寄せておくほうが結局は早い、と何度も痛感してきました。私自身、この記事のチェックリストは実際に手元のコードで詰まるたびに書き足してきたものです。

API の挙動を深く理解するうえでは、Claude API トークンカウントとコスト最適化ガイドClaude API 高度な Tool Use 完全ガイド も参考になります。あわせて Claude API ストリーミング Tool Use 実装 を読んでおくと、ストリーミング系の落とし穴に強くなれます。

次にこの記事を活かす一歩として、今手元にある stop_sequences を使ったコードを 1 つ取り出し、stop_reason をログに出すコードを 3 行追加してみてください。それだけで、次にこの問題に遭遇したときの切り分け時間は 10 分の 1 になります。

シェア

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

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

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

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

API & SDK2026-04-26
Claude APIストリーミングが途中で切れる — 症状別の診断と修正パターン
Claude APIのストリーミングレスポンスが途中で止まる原因をタイプ別に診断し、タイムアウト・レート制限・max_tokens・クライアント実装バグそれぞれの修正コードを解説します。
API & SDK2026-06-27
Claude API のストリーミングが「エラーも出さずに」止まるとき — 沈黙する停止を検知して途中から続ける運用メモ
Claude API のストリーミングが例外も出さずに無言で止まる「沈黙停止」を、トークン間隔のウォッチドッグで検知し、受信済みテキストを引き継いで途中から再開する実装と、長時間の自動運用で崩れないためのタイムアウト予算の設計をまとめます。
API & SDK2026-05-28
Claude API のストリーミングで tool_use の引数が JSON パースに失敗する原因と対処
Claude API のストリーミングで tool_use を受け取ると、なぜか JSON.parse が SyntaxError を返す。input_json_delta の正しい組み立て手順と、再接続時の取りこぼし対策をまとめます。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →