claude -p や Agent SDK で Claude Code を無人で動かしているスクリプト書き向け
claude -p や Agent SDK 経由で Claude Code を夜間バッチや自動スクリプトとして動かしている場面で、現状デフォルトで動いてしまうため意識する必要がある。新しくスクリプトを書く場合は、起動用スクリプトの先頭に <code>export CLAUDE_CODE_ENABLE_TASKS=1</code> を1行入れて、最初から TaskCreate 系を呼ばせる方に倒しておく。既存のバッチがログ形式に依存していて切り替えに躊躇する場合は、staging 環境で1週間並行運用してログ差分を確認してから本番に出す。
Claude Code が「セッション中にやることリスト(TODOチェックリスト)」を内部で持っておくためのツールです。タスクを増やしたり、進捗を「やった」「やってる最中」「これから」に振り分けたり、Claude 自身が長い作業の途中で道具として呼びます。
ただし今や Deprecated(非推奨) 扱い。公式ドキュメントは「TaskCreate / TaskGet / TaskList / TaskUpdate の方を使え」と明言していて、TodoWrite は移行期にしか残らない過渡期ツールです。
噛み砕くと
引っ越し業者が現場に来た時、壁に貼る「今日の作業表」を想像してください。「①家具を運び出す ②冷蔵庫だけ別便で ③ベッドは解体」みたいな表を1枚作って、終わったらシールを貼っていく。あれの Claude 版が TodoWrite です。
ただし新型の作業表が現場にもう届いている。それが TaskCreate 系の4点セットで、画面の前に人がいるセッションではみんなそっちを使っている。古い作業表が残っているのは、夜中に無人で勝手に動く現場、つまり後述する無人実行モードの中だけ、というのが今の状況です。
大事な前提:これは Claude 自身が裏で呼ぶ道具。ユーザーが叩くものではない
TodoWrite はターミナルでユーザーが TodoWrite と打って呼び出すコマンドではありません。Claude Code が長めの作業を頼まれた時に、内部で「ここはチェックリスト化しよう」と判断して自動で呼ぶ内蔵ツールです。ユーザー側に見えるのは、画面に出てくる小さなTODOパネルだけ。
そして今は呼ばれる場面が2つに分かれています。画面の前に人がいるインタラクティブセッションでは、TodoWrite はもう呼ばれません。新しい TaskCreate 系が代わりに動きます。一方で、夜間バッチや自動スクリプトで claude -p や Agent SDK 経由で動かしている時だけは、まだ TodoWrite が初期設定として動いています。
「夜中に cron で 10 プロジェクト連続レビュー」を例に、実際の手順を見る
1晩で10個のプロジェクト一式を順番にコードレビューさせる、というバッチを想定します。claude -p で1プロジェクトにつき5サブタスクを回す構成で、5サブタスクの内訳は「依存関係の確認 / 命名規約 / テスト網羅 / 安全性 / ドキュメント差分」です。ここで TodoWrite がどう動き、どう書き換えればいいかを順に見ます。
ステップ1: cron で深夜2時に Python スクリプトを走らせる
毎晩2時に動く Python スクリプトが、プロジェクト一覧を読んで1個ずつ claude -p を起動します。
# crontab -e
0 2 * * * /home/me/review-batch/run.sh >> /var/log/review.log 2>&1
起動用スクリプト側はこんな雰囲気。
for repo in $(cat /home/me/review-batch/repos.txt); do
cd "$repo"
claude -p "このプロジェクトを5観点でレビューして。
依存関係 / 命名 / テスト / 安全性 / ドキュメント差分 の順で進めて、
それぞれ完了したら次に進んで" \
--output-format stream-json > "/var/log/review-$(basename $repo).log"
done
ステップ2: Claude 側が勝手に TodoWrite で5サブタスクをリスト化する
レビュー指示を受けた Claude は、自分の判断で TodoWrite を呼び、5つのサブタスクを内部チェックリストに登録します。実際にログに残るのはこんな感じの中間出力です。
[TodoWrite]
□ 依存関係の確認 (status: pending)
□ 命名規約のチェック (status: pending)
□ テスト網羅の確認 (status: pending)
□ 安全性のレビュー (status: pending)
□ ドキュメント差分 (status: pending)
ユーザーは何も指示していません。Claude が「これは5段階に分けた方がよさそうだ」と判断して、勝手にチェックリストを作っています。
ステップ3: 1サブタスクずつ in_progress → completed に書き換える
各サブタスクに取りかかる時に TodoWrite を再び呼び、「今やってる」「終わった」の状態を更新します。ログにはこんな遷移が並びます。
[TodoWrite update]
✓ 依存関係の確認 (status: completed)
▶ 命名規約のチェック (status: in_progress)
□ テスト網羅の確認 (status: pending)
□ 安全性のレビュー (status: pending)
□ ドキュメント差分 (status: pending)
ログを後で見返すと、どこまで進んでどこで止まったかが分かるようになっています。これが TodoWrite の本来の役割。
ステップ4: ここで初心者がやりがちな勘違いがある
「ログに TodoWrite が出てきたから、これは新規スクリプトでも使うべきだ」と読み取ってしまう人がいます。逆です。新しく書くなら TaskCreate 系に寄せるのが公式の指示。TodoWrite はインタラクティブセッションでは既に置き換え済みで、claude -p と Agent SDK だけ「TodoWrite が初期設定として残っている」状態。いずれ削除されます。
ステップ5: 起動時設定を1つセットして TaskCreate 系に切り替える
夜間バッチ側で TaskCreate 系を呼ばせたい時は、起動用スクリプトの先頭にこの1行を足します。
export CLAUDE_CODE_ENABLE_TASKS=1
for repo in $(cat /home/me/review-batch/repos.txt); do
cd "$repo"
claude -p "..." --output-format stream-json > ...
done
これだけで claude -p や Agent SDK 経由の Claude も TaskCreate / TaskGet / TaskList / TaskUpdate を呼ぶように切り替わります。TodoWrite が削除される前に、このスイッチを倒しておくのが移行の正攻法です。
ステップ6: TaskCreate 系に切り替えた後のログを確認する
切り替え後に同じバッチを動かすと、ログの中身が変わります。TodoWrite の単一呼び出しではなく、TaskCreate で作って TaskUpdate で進めて、必要なら TaskList で一覧、TaskGet で詳細という4手の流れに変わります。チェックリストとしての見え方は近いものの、内部の取り回しは作る/見る/一覧/更新で役割が分かれているので、後から「何が遅かったか」「どのタスクで止まったか」を追いやすくなります。
つまり TodoWrite は何をしてくれるのか
- やってくれる: セッション内のTODOチェックリストを Claude 自身が管理する。長めの作業を5段階に切って「やった / やってる / これから」を内部で持つ
- やってくれる:
claude -pや Agent SDK 経由の夜間バッチで、現状デフォルトで動く。CLAUDE_CODE_ENABLE_TASKS=1を入れない限り、無人実行ではこれが呼ばれる - やってくれない: ユーザーが
TodoWriteと打って直接呼び出すこと。Claude 自身が裏で呼ぶ内蔵ツールなので、ユーザー操作のコマンドではない - やってくれない: セッションをまたいだ永続TODO管理。あくまで1セッション内のチェックリストで、Claude を抜けたら消える
- 意味が薄い場面: 新規に
claude -pや Agent SDK のスクリプトを今から書く場面。CLAUDE_CODE_ENABLE_TASKS=1をセットして最初から TaskCreate 系に寄せた方が、削除日が来ても書き換えなくて済む
使いどころ3シナリオ(具体題材で再現)
シナリオ1: 夜中に cron で10プロジェクト連続レビュー(実演で扱った例)
10個のOSSプロジェクトを毎晩2時にレビューさせるバッチ。1プロジェクトで5サブタスクを進める間、Claude が TodoWrite で内部チェックリストを作って進捗を記録します。ログを翌朝確認した時、「3個目のプロジェクトの『テスト網羅の確認』で詰まっていた」が分かるのは TodoWrite のおかげ。ただし新規に書くなら最初から CLAUDE_CODE_ENABLE_TASKS=1 をセットして TaskCreate 系に寄せておく方が、半年後に書き換えなくて済みます。
シナリオ2: Agent SDK で「毎朝50件の Issue を分類するスクリプト」を書く時
GitHub の Issue 50件を毎朝自動で読んで、「バグ / 機能要望 / 質問 / 重複 / 対応不要」に振り分けるスクリプトを Agent SDK で書く場面。50件分の Issue それぞれで5分類のサブタスクが立ちます。今のままなら TodoWrite が動きますが、これも新規開発なら CLAUDE_CODE_ENABLE_TASKS=1 を最初に入れる。スクリプトを置く Python ファイルの先頭で os.environ["CLAUDE_CODE_ENABLE_TASKS"] = "1" を1行入れるだけで切り替わります。
シナリオ3: 既存の夜間バッチが TodoWrite 前提で動いている時の移行手順
1年前に書いたバッチが TodoWrite のログ形式に依存している場面。たとえばログを grep して「何件 in_progress で止まったか」を集計しているような構成です。これを TaskCreate 系に乗せ替える時、いきなり全部切り替えるのではなく、staging 環境で CLAUDE_CODE_ENABLE_TASKS=1 を1週間だけ入れて、ログ形式の差分を確認してから本番に出すのが安全。TodoWrite の削除日は公式から明示されていませんが、Deprecated 宣言が出ている以上、半年〜1年スパンで考えておく価値があります。
初心者が踏みやすい落とし穴
- 新しく書くスクリプトで TodoWrite を前提にする。Deprecated 宣言が出ているツールに新規依存を作らない。
CLAUDE_CODE_ENABLE_TASKS=1を最初に入れて TaskCreate 系で書く - インタラクティブセッションでも TodoWrite が呼ばれていると勘違いする。今は違う。画面の前に人がいるセッションでは既に TaskCreate 系がデフォルト。TodoWrite が現役で残っているのは
claude -pと Agent SDK の無人実行モードだけ - TodoWrite と TaskCreate を混同して呼ぶ。名前は似ているが、TodoWrite は単発呼び出しで内部リストを書き換える1ツール、TaskCreate 系は作る/見る/一覧/更新で役割が4分割されている。ログを見て両方が混在していたら、起動時設定が中途半端に効いている疑いがある
- 削除日を待ってから移行しようとする。公式は「TodoWrite が削除される前に切り替えろ」と明言しており、削除日は予告なしで来ても文句が言えない位置。Deprecated 宣言が出た時点で移行スイッチを倒しておく
- セッションをまたいだ永続的なTODO管理に使おうとする。これはあくまで1セッション内のチェックリスト。Claude を抜けたら消える。日をまたぐ進捗管理がしたいなら、Notion や GitHub Issues などの外部タスク管理ツールに出力させる設計にする
- ログから直接 TODO 状態を grep して別システムに連携している。TaskCreate 系に切り替えるとログ形式が変わるので、grep ベースの集計スクリプトが壊れる。移行前に staging で1週間並行運用してログ差分を確認する
- 起動時設定を export し忘れる。
CLAUDE_CODE_ENABLE_TASKS=1をターミナルで打っただけだと、子プロセスに伝わらない。exportを付ける、または cron の場合は起動用スクリプト内でexport行を入れる
書き方
(ユーザーが直接打つコマンドではない。Claude Code が長めの作業中、自分の判断で内部のTODOチェックリストを更新するために自動で呼び出す内蔵ツール)
現在の発火条件:
- インタラクティブセッション → 呼ばれない(TaskCreate / TaskGet / TaskList / TaskUpdate が代わりに動く)
- claude -p の無人実行 → デフォルトで TodoWrite が動く
- Agent SDK 経由(Python / TypeScript) → デフォルトで TodoWrite が動く
移行スイッチ:
export CLAUDE_CODE_ENABLE_TASKS=1
この起動時設定を 1 にしておくと、claude -p と Agent SDK 経由でも TaskCreate 系が呼ばれるようになる。Deprecated 宣言が出ている以上、TodoWrite が削除される前にこのスイッチを倒しておくのが安全。
公式 verbatim: 「Deprecated in favor of TaskCreate, TaskGet, TaskList, and TaskUpdate. Interactive sessions already use the Task tools by default. When you run claude -p or use the Agent SDK, TodoWrite is still the default. Set CLAUDE_CODE_ENABLE_TASKS=1 to switch those to the Task tools before TodoWrite is removed.」(permission required: No)
やってみるとこうなる
入力
# claude -p で5サブタスクをまとめて指示する例
claude -p "このプロジェクトを5観点でレビューして。
依存関係 / 命名 / テスト / 安全性 / ドキュメント差分 の順で進めて、
それぞれ完了したら次に進んで"
出力例
(Claude が自分の判断で TodoWrite を呼び出し、ログにこんな中間出力が残る)
[TodoWrite]
□ 依存関係の確認 (status: pending)
□ 命名規約のチェック (status: pending)
□ テスト網羅の確認 (status: pending)
□ 安全性のレビュー (status: pending)
□ ドキュメント差分 (status: pending)
各サブタスクに取りかかる時に再度呼び出される。
[TodoWrite update]
✓ 依存関係の確認 (status: completed)
▶ 命名規約のチェック (status: in_progress)
□ テスト網羅の確認 (status: pending)
□ 安全性のレビュー (status: pending)
□ ドキュメント差分 (status: pending)
CLAUDE_CODE_ENABLE_TASKS=1 を入れて切り替えた後は、TaskCreate / TaskUpdate / TaskList / TaskGet の4手で同じことが行われる。
このページに出てきた言葉
- Deprecated
- 「もう使わないでね」と公式が宣言した状態。すぐ消えるとは限らないが、将来のバージョンで削除予定なので、新しく書くスクリプトでは使わない方がいい
- TaskCreate / TaskGet / TaskList / TaskUpdate
- TodoWrite の置き換え先として新しく用意された4点セットのツール。タスクを作る / 中身を見る / 一覧する / 更新する、で役割が分かれている
- インタラクティブセッション
- 画面の前に人が座っていて、Claude とリアルタイムでやり取りする普通の使い方
- claude -p
- Claude Code を「人が画面の前にいない状態」でスクリプト的に1回だけ動かす呼び出し方。返事を受け取って即終了する
- Agent SDK
- Claude Code の中身を自分のプログラムから直接呼び出せるようにする開発キット。Python や TypeScript のコードに組み込んで、無人で連続作業させる時に使う
- headless
- 画面表示なし・人の入力なしでプログラムを動かすこと。<code>claude -p</code> や Agent SDK 経由の動かし方はこれに当たる
- cron
- パソコンやサーバーに「毎晩2時にこれを動かして」みたいな定期実行をお願いする仕組み。Linux や Mac に標準で入っている
- CLAUDE_CODE_ENABLE_TASKS=1
- この値を 1 にしておくと、<code>claude -p</code> と Agent SDK でも TodoWrite ではなく TaskCreate 系が呼ばれるようになる、移行用のスイッチ
- export
- パソコンに「この設定値を、これから動かす別プログラムにも伝えてね」とお願いする命令。これを付け忘れると子プロセスに値が届かない
- permission required: No
- そのツールを呼び出す時にユーザーへの許可ダイアログが出ないこと。TodoWrite はこれに該当するので、無人実行中でも確認なく発火する