Claude Code をスクリプト経由で叩いていて、応答を一気に受け取るのではなくトークン単位でリアルタイムに流したい人向け
Claude の応答を Claude.ai 風にタイプライター演出で画面に流したい時、CI ログに「動いてる感」を出したい時、独自 Web UI でライブ応答を実装したい時に、--print と --output-format stream-json と一緒に指定して使う
Claude Code を claude -p(プリントモード)で叩いた時に、Claude の応答を1ターン完了ごとにドサッと受け取るのではなく、生成中のトークン1つひとつを逐次受け取るための起動スイッチが --include-partial-messages です。Claude.ai の画面で文字がパラパラと出てくる、あの「タイプライター風」の挙動を、自分の CLI ツールや CI ログでも再現できるようになります。
単体では動きません。--print と --output-format stream-json の2つが揃って初めて意味を持つ、いわば「セットで使う3点目」のスイッチです。
噛み砕くと
普通の claude -p "質問" は、レストランで料理を頼んで「全部できあがってから一皿で持ってきてもらう」イメージです。Claude が考え終わって応答を全部組み立て終わった後に、まとめて1回返ってきます。
--include-partial-messages を足すと、これが「キッチンから1ピースずつ運ばれてくる」モードに切り替わります。Claude が「料」「理」「ブ」「ロ」「グ」と1文字単位で吐き出すたび、その1ピース1ピースがリアルタイムで手元に届きます。受け取った側で1文字ずつ画面に流せば、Claude.ai のあのライブ感がそのまま再現できる、という話です。
用途は主に2つあります。1つは CI のログを眺めている人に「止まってない、ちゃんと進んでる」と見せる用。もう1つは自前 UI でライブ出力を演出する用です。
大事な前提:単体では効かない、3点セットで使う
公式の説明文には「Requires --print and --output-format stream-json」と明記されています。--include-partial-messages だけ付けても、対話モードで claude を裸で起動した場合は無視されますし、出力形式が text や json でも効きません。さらに headless docs の推奨例ではこれに --verbose が加わり、計3〜4点セットで使うのが定石です。
「料理ブログの長文レシピを1文字ずつ流す CLI」を例に、実際の手順を見る
料理ブログの記事下書きとして「サバの味噌煮のレシピ説明文を 400 字くらいで書いて」と Claude に頼み、その出力を画面に1文字ずつタイプライター風に流す。そういう小さな CLI ツールを作る流れで、--include-partial-messages の効き目を順に見ていきます。
ステップ1: まず partial なしの stream-json を見る
比較対象として、partial なしで stream-json だけ流してみます。--verbose も付けて、初期化イベントなども含めた全量を見ます。
$ claude -p "サバの味噌煮のレシピ説明を 400 字で書いて" \
--output-format stream-json --verbose
返ってくるのは1行1個の JSON オブジェクトで、内訳はだいたい次のような構成です。
- 1行目:
system/init。セッション開始の通知で、モデル名や読み込んだプラグイン情報が入る - 2行目あたり:
assistantメッセージのブロック。応答テキストが丸ごと1個分入っている - 最後:
result。消費トークンや合計コストのまとめが入る
つまり partial なしだと、応答テキストは「完成済み 400 字」が1つの JSON 行にまとめて入った状態で来ます。これだとタイプライター演出はできません。
ステップ2: --include-partial-messages を足す
ここで本題のスイッチを追加します。
$ claude -p "サバの味噌煮のレシピ説明を 400 字で書いて" \
--output-format stream-json --verbose --include-partial-messages
出力の行数が一気に増えます。1ターン分の応答が「完成品 1 行」ではなく、生成中の stream_event という型の行がドバドバ流れてくる状態に変わります。各行はだいたいこんな形です。公式 docs の verbatim から、必要部分だけ抜粋します。
{"type":"stream_event","event":{"delta":{"type":"text_delta","text":"サ"}}, ...}
{"type":"stream_event","event":{"delta":{"type":"text_delta","text":"バ"}}, ...}
{"type":"stream_event","event":{"delta":{"type":"text_delta","text":"の"}}, ...}
1イベント = 1〜数文字。これを受け取り側で順に画面に出せば、タイプライター演出のできあがりです。
ステップ3: jq で text_delta だけ抜く
ただし、流れてくるのは stream_event だけではありません。system/init や system/api_retry(リトライ通知)など、制御系のイベントも同じ stream に混ざります。テキストだけ拾いたいなら絞り込みが必要です。
headless docs の公式例がそのまま使えます。
$ claude -p "Write a poem" --output-format stream-json --verbose --include-partial-messages | \
jq -rj 'select(.type == "stream_event" and .event.delta.type? == "text_delta") | .event.delta.text'
jq -rj の -r は「文字列をクォート無しで生のまま出す」、-j は「行末改行を付けない」という意味です。これで Claude が吐いた text_delta の中身だけが、改行なしでベタッと連結されながら流れ、画面上ではちょうど1文字ずつ生えてくるように見えます。
ステップ4: 改行ありの -r と何が違うか
ここで初心者がやりがちな勘違いがあります。-j を外して jq -r だけにしても text_delta は抜けますが、各 delta の後ろに改行が勝手に入ってしまいます。1文字目「サ」、改行、「バ」、改行……となって、タイプライター風どころか縦に1文字ずつ並ぶ妙な見た目になります。連結したいなら必ず -rj です。
ステップ5: 制御イベントを混ぜないコツ
上の select 句が大事で、これがないと system/init の中身、つまり JSON オブジェクト全体まで画面に流れ込みます。type == "stream_event" と event.delta.type == "text_delta" の2段でしっかり絞ること。テキスト以外にもツール呼び出しの delta なども流れてくるので、type で篩にかける癖を最初から付けるのが安全です。
ステップ6: --include-hook-events と組み合わせる
同じ stream に hook ライフサイクル、つまりツール実行の前後通知などのイベントも乗せたい場合、別スイッチの --include-hook-events を同時に付けます。両方 on にすると、トークン delta と hook イベントが同じ stream に混ざって流れます。それぞれ独立して on/off できるので、「トークンは見たいけど hook はノイズ」みたいな運用も普通にできます。
つまり --include-partial-messages は何をしてくれるのか
- やってくれる: 生成中の応答を
stream_eventとして小刻みに流す。受け取り側でリアルタイム表示ができる - やってくれない: 単体起動・対話モード・
--output-format textやjsonでは効かない。stream-json 必須 - 意味が薄い場面: 結果テキストを変数に貯めて1回まとめて使うだけのスクリプト。最後に
resultから取ればいい話で、partial を有効化する理由が無い
使いどころ3シナリオ(具体題材で再現)
シナリオ1: 料理ブログの「AI レシピ生成」CLI でタイプライター演出
料理ブログの管理画面に「AI で説明文を下書きする」ボタンを置くとして、その裏で動かす CLI に --include-partial-messages を仕込みます。jq -rj 'select(.type=="stream_event" and .event.delta.type?=="text_delta") | .event.delta.text' の組み合わせで、サバの味噌煮 400 字が1文字ずつ画面に生えてくる UX が再現できます。書いてる人、つまりブログ運営者に「AI が今まさに考えてくれてる」感を伝えるための演出として有効です。
シナリオ2: CI で長い review コメントの進捗を可視化する
GitHub Actions などで claude -p "この PR のセキュリティ問題を全部書き出して" みたいな長文出力を回す時、partial なしだと CI ログは「数分間まっさら → ドンと一発で全文出現」になり、見ている側が「止まってない?」と不安になります。--include-partial-messages + jq で text_delta を流せば、ログがじわじわ伸び続けるので「動いてる」が一目で伝わります。
シナリオ3: 独自 Web UI で Claude.ai 風のライブ応答を作る
自前の Web 画面で Claude Code をバックエンドに使う場合、フロントに WebSocket で text_delta を1個ずつ送り続ければ、Claude.ai のあのライブ生成 UI がそのまま自前で再現できます。--include-partial-messages で stream を出させて、サーバ側で type=="stream_event" だけ拾って WebSocket に流す、というのが定番構成です。
初心者が踏みやすい落とし穴
- 単体起動では効かない。
--printと--output-format stream-jsonをセットで指定しないと、Claude Code は--include-partial-messagesを黙って無視する挙動になる。エラーで止まってくれるとは限らないので、出力が変だと思ったらまず3点セットを確認 - text や json では効かない。
--output-formatが stream-json じゃないと partial の意味が消える。jsonと書きたくなるが、それだと普通のターン完了 JSON しか返ってこない - 制御イベントが混ざる。
stream_event以外にsystem/initやsystem/api_retryも同じ stream に来る。jqのselectで type を絞らないと制御メタデータが画面に漏れる - delta は text_delta だけじゃない。ツール呼び出しの delta など他の type も乗ってくる。「テキストだけ取りたい」なら
event.delta.type == "text_delta"まで条件を絞る jq -rjとjq -rを間違えると見た目が崩壊する。-rだけだと delta ごとに改行が入って縦並びになる。タイプライター風に連結したいなら必ず-rj- トークン量がかなり多くなる。応答 400 字でも数百行の stream_event が流れる。ログにそのまま吐き続けると CI のログ容量を食う。本番運用では絞り込んだ後の text だけログに残すのが無難
--verboseを忘れがち。cli-reference の「Requires」句には verbose は書かれていないが、headless docs の推奨例は3点セットに verbose を足した4点構成。困ったら verbose も付けておくのが安全- 「partial 有効 = 全部の応答が partial で来る」と思い込まない。stream-json の中には完成済みのターン全体ブロック、つまり assistant メッセージや result も依然として混ざる。1イベント = 1個の JSON 行という前提だけで、何が来るかは type を見て判断する
書き方
claude -p "質問" --output-format stream-json --verbose --include-partial-messages
やってみるとこうなる
入力
claude -p "サバの味噌煮のレシピ説明を 400 字で書いて" --output-format stream-json --verbose --include-partial-messages | jq -rj 'select(.type == "stream_event" and .event.delta.type? == "text_delta") | .event.delta.text'
出力例
サバの味噌煮は、家庭で作る煮魚の中でも特に人気の高い一品です。新鮮なサバの切り身を用意し、熱湯にさっとくぐらせて臭みを落とすところから始めます。鍋に水、酒、みりん、砂糖を入れて煮立て、生姜の薄切りとサバを加えて落とし蓋をして 10 分。最後に味噌を溶き入れ、煮汁にとろみが付くまで弱火で 5 分ほど煮詰めれば完成です。
(実際には text_delta が1〜数文字ずつ jq -rj 経由で連結され、画面には上の文章が1文字ずつ生えてくるように見える)
このページに出てきた言葉
- プリントモード
- <code>claude -p "質問"</code> の形で叩く非対話モード。対話画面を開かず、1往復ぶんの応答だけ受け取って終わる呼び出し方
- stream-json
- 出力形式の1つ。1行1個の JSON オブジェクトを並べた形で、生成中の途中経過を流しながら出せる
- stream_event
- stream-json の中で「生成途中の1ピース」を表す行の種類。<code>type</code> フィールドの値がこれになっている
- text_delta
- stream_event のうち、応答テキストの増分(次に出る数文字)を運ぶタイプ。<code>event.delta.type</code> がこれの行を拾えば本文だけ取れる
- jq
- JSON を整形・抜き出しするための小さな CLI ツール。<code>-r</code> でクォートを外し、<code>-j</code> で改行を付けない連結出力ができる
- headless
- 画面を持たずに動かすという意味。CI スクリプトや SDK 経由の呼び出しがこれにあたる
- hook ライフサイクル
- Claude Code がツールを呼ぶ前後などの節目で発火する仕掛け。<code>--include-hook-events</code> でこれも stream に乗せられる