TaskCreate / TaskUpdate ツールを本番運用していて、タスク作成・完了のタイミングで Linear や Notion、Slack と連携処理を入れたい中級開発者向け
個人プロジェクトでもチーム開発でも、Claude が刻んだタスクを Linear / Notion / Slack / 自社ツールと同期したい、または「テストが通るまで完了扱いさせない」「特定条件のタスクは作成自体を止める」みたいなガードを掛けたい場面で、.claude/settings.json の hooks セクションに登録して使う
TaskCreated と TaskCompleted は、Claude Code の TodoWrite 後継4セットのうち、TaskCreate が新しいタスクを刻んだ瞬間と、そのタスクが完了マークに変わった瞬間に、こちら側のスクリプトを差し込めるフックです。後継4セットは TaskCreate、TaskList、TaskGet、TaskUpdate の4つで、この辞書エントリで扱うのはその裏に張る通知の仕組みのほうです。Claude がタスクを切ったら Linear に同期、完了したら Slack に通知、みたいな連携処理をここに繋げます。
同じ TodoWrite 後継4セット系の pretooluse / posttooluse はツール呼び出しの前後で発火しますが、こちらはタスク管理の状態変化そのものがきっかけ。粒度が一段違います。
そもそもフックって何のために存在するのか
Claude Code が何かをやろうとした瞬間に、こちら側の自作スクリプトを割り込ませる仕組みがフックです。ファイル編集の前後、タスク作成の瞬間、完了の瞬間、というふうにイベントごとに名前が付いていて、その名前の場所にスクリプトを登録すると、Claude がそのイベントを起こすたびに自動でスクリプトが走ります。
TaskCreated と TaskCompleted は、その「イベントの名前」のうち2つ。Claude がタスク管理ツールを動かしたタイミングを横から覗いて、Linear や Notion、Slack、自社の社内ツールといった外部サービスと同期するために使います。
「cooking-blog でレシピページを追加する」で実際の挙動を見る
例として、レシピを淡々と載せていく cooking-blog プロジェクトで、Claude に「『鯖味噌煮』のレシピページを追加して」と依頼した場面を追います。Claude は内部で TaskCreate を呼んで、自分用の作業タスクを刻みます。ここに TaskCreated を仕掛けます。
ステップ1: TaskCreated フックを settings.json に登録
プロジェクト直下の .claude/settings.json にフック登録します。書式は他のフックと同じで、イベント名・実行コマンド・タイムアウトを並べます。
{
"hooks": {
"TaskCreated": [
{
"command": "node .claude/hooks/sync-to-linear.js",
"timeout": 5000
}
]
}
}
ここで初心者がやりがちな勘違い。matchers を一緒に書いて「TaskCreate ツールのときだけ走らせたい」と絞り込もうとしても、TaskCreated と TaskCompleted はmatcher を書いても黙って無視される仕様です。公式 docs にも明記されていて、matcher を書いても silently ignored と書かれている。フィルタしたいなら、後述するように受け取った JSON を読んで script 内で自分で振り分けます。
ステップ2: sync-to-linear.js が受け取る JSON を読む
Claude が TaskCreate を呼ぶと、TaskCreated フックの script の標準入力(stdin)に JSON が流れてきます。中身は task_id、task_description、task の親情報など。スクリプト側はこれを読んで「どんなタスクが作られたか」を見て、Linear API を叩いて同じタスクを Linear 側にも作る、という流れです。
#!/usr/bin/env node
const input = require('fs').readFileSync(0, 'utf-8');
const payload = JSON.parse(input);
// payload.task_description などを Linear に POST
ステップ3: TaskCompleted で Slack に通知する
同じ settings.json に TaskCompleted を追加。レシピページの追加が終わってタスクが完了マークに変わった瞬間、Slack の #cooking-blog チャンネルに「『鯖味噌煮』のページが追加されました」と投げる構成です。
"TaskCompleted": [
{
"command": "node .claude/hooks/notify-slack.js",
"timeout": 5000
}
]
ステップ4: 完了をブロックしたい場合は exit 2 を返す
レシピページの追加タスクの中に「画像の代替テキストを書く」というサブ作業が含まれていて、それが未着手のままタスクを完了させようとした場面を考えます。TaskCompleted フックで「代替テキストが空ならまだ完了扱いにしない」とチェックして、未完なら script から exit 2 を返します。すると Claude 側のタスク完了処理が止まり、stderr に書いた理由が Claude にフィードバックとして渡って、Claude は「ああ、まだ代替テキストが空だな」と理解して残作業に戻ります。
#!/usr/bin/env node
const payload = JSON.parse(require('fs').readFileSync(0, 'utf-8'));
if (!payload.task_description.includes('alt済')) {
process.stderr.write('alt属性が未完です。先に埋めてください。');
process.exit(2);
}
ステップ5: TaskCreated で exit 2 を返すと「タスク作成自体が無かったこと」になる
TaskCreated 側の exit 2 はもう一段強くて、公式 docs の言葉どおり「Rolls back the task creation」。つまりタスクの作成自体が rollback されて、Claude 視点では「タスクを作ろうとしたが作られなかった」状態に戻ります。例えば「タスクのタイトルに『レシピ』という単語が入っていなければ全部弾く」みたいなガードを掛けたい場面で使えますが、Claude がタスクを作れない状態でループする副作用に注意です。
ステップ6: 全体を止めたいときは continue: false
exit 2 は task 単位の差し戻し。これに対して、JSON で {"continue": false, "stopReason": "..."} を返すと、task ではなく Claude のセッション(teammate)全体が止まります。Stop フックと同じ重さの核弾頭級の応答で、ここを間違えて使うと session ごと落ちます。
つまり TaskCreated と TaskCompleted は何をしてくれるのか
- やってくれる: Claude がタスクを刻んだ瞬間・完了させた瞬間に、こちら側のスクリプトを走らせる。Linear / Notion / Slack 等の外部サービスとの同期、完了条件チェックの自動化、タスクの差し戻し、セッション全体の中断、まで一通りカバーする
- やってくれない: 「TaskCreate ツールのときだけ」「特定の親タスクのときだけ」みたいな事前フィルタはしてくれない。matcher 非対応なので、絞り込みは script 内で stdin の JSON を読んで自前で判定する必要がある
- 意味が薄い場面: Claude が単発でファイルを1つ編集するだけのワンショット作業。タスク管理ツールが呼ばれないので、TaskCreated も TaskCompleted も発火しない。短い相談・Q&A ベースの使い方ではセットしても何も起きない
使いどころ3シナリオ
シナリオ1: cooking-blog の編集タスクを Linear に同期する
個人開発の cooking-blog でも、レシピの追加・修正・写真差し替え、と作業項目を Linear に溜めて管理しているケース。Claude にまとめて「3品分のレシピページを追加して、それぞれ alt 属性も書いて」と頼むと、Claude は内部で TaskCreate を3回呼んでサブタスクを切ります。TaskCreated フックで script を走らせ、Linear API に同じ3タスクを作っておけば、Linear 画面を見るだけで「いま Claude が何をやってる最中か」が把握できます。
シナリオ2: TaskCompleted で Slack のチャンネルに完了報告
チームで共同編集しているプロジェクトで、Claude にレシピ追加を任せている裏でレビュアーが別作業をしている場面。TaskCompleted フックから Slack Webhook を叩き、「『鯖味噌煮』のレシピページが完了。レビューお願いします」と #cooking-blog チャンネルに自動投稿します。レビュアーは Claude のセッション画面を覗かなくても、Slack で完了タイミングだけ把握できます。
シナリオ3: 失敗テストが残っていれば TaskCompleted で完了させない
cooking-blog にレシピ表示の自動テスト(Vitest)が組まれている場面。Claude が「レシピページ追加タスク」を完了させようとした瞬間、TaskCompleted フックから npm test を走らせ、1件でも失敗していれば exit 2 で完了をブロック。stderr に「テストが2件落ちています」と書けば、Claude はその通知を受け取って残作業に戻ります。人が画面を見ていなくても「テストが通るまで完了扱いさせない」というガードが効きます。
初心者が踏みやすい落とし穴
- matcher を書いても効かない。TaskCreated と TaskCompleted は matcher 非対応で、書いても公式 docs いわく silently ignored。絞り込みは script 側で stdin JSON を読んで自分で判定する。
Stopフックと同じ性質と覚えると整理しやすい - TaskCreated の exit 2 は「タスク作成のロールバック」。完了を止めるだけの TaskCompleted と違い、こちらは作成そのものを巻き戻す強さがある。「タスクが作れない」状態で Claude が同じ TaskCreate を何度も再試行してループしやすい。条件は十分に緩めにする
- continue: false はセッション全体を止める核弾頭。task 単位の差し戻しではなく Claude のセッション(teammate)全体を停止して Stop フック相当の振る舞いになる。task 単位は exit 2、session 中断は continue: false、と用途を分けて使う
- script のタイムアウトはきつめに設定する。Linear や Slack、Notion といった外部 API を叩く構成だと、ネットワーク不調で script が固まり、その間 Claude のタスク処理も止まる。timeout を 5 秒〜10 秒で切って、失敗時は静かに諦める設計が安全
- TaskCreated と TaskCompleted は毎タスクで発火。サブタスクを大量に作る依頼を出すと、その回数分 script が走る。Linear / Slack の API 制限に当たりやすいので、bulk 化(まとめて送る)や rate-limit 配慮を script 側に入れる
- ツール本体の TaskCreate / TaskUpdate と混同しない。フックは「Claude がそのツールを呼んだ瞬間に反応するイベント」で、ツール本体ではない。ツール側の挙動は
taskcreate-tasklist-taskstopやtaskupdateの辞書エントリを参照 - TodoWrite 系の旧フックとの違い。TodoWrite は単一ツールに対する PreToolUse / PostToolUse で受けていたが、後継4セットでは「タスク状態の変化」イベントとして TaskCreated / TaskCompleted が独立した。matcher が効かないこと、rollback が組み込まれていることが旧フックには無い大きな差
書き方
{
"hooks": {
"TaskCreated": [{ "command": "...", "timeout": 5000 }],
"TaskCompleted": [{ "command": "...", "timeout": 5000 }]
}
}
やってみるとこうなる
入力
Claude が TaskCreate ツールで「鯖味噌煮のレシピページを追加」というタスクを作成 → TaskCreated フックの script の stdin に task_id・task_description を含む JSON が渡される
出力例
exit 0 で次へ進む / exit 2 で TaskCreated は作成ロールバック、TaskCompleted は完了ブロック(stderr の文字列が Claude にフィードバックされる)/ JSON {"continue": false, "stopReason": "..."} を返すと teammate(Claude のセッション)全体を停止
このページに出てきた言葉
- フック
- Claude Code が特定の動作をする瞬間に、自作スクリプトを差し込んで走らせる登録枠。<code>.claude/settings.json</code> の <code>hooks</code> セクションに書く
- matcher
- フックを「特定のツールのときだけ」と絞り込む設定欄。TaskCreated / TaskCompleted では書いても黙って無視される
- rollback
- 直前の処理を「無かったこと」にして元に戻す動き。TaskCreated で <code>exit 2</code> を返すとタスク作成自体が rollback される
- exit code
- スクリプト終了時の状態を表す数字。0 が正常、2 がフックでの「ブロック」を意味する Claude Code 固有の取り決め
- teammate
- Claude Code が動かしている Claude セッションのこと。<code>continue: false</code> はこれ全体を止める
- stopReason
- <code>continue: false</code> と一緒に返す「なぜ止めたか」の説明文。Claude のログや UI に表示される
- stdin
- スクリプトの標準入力。Claude Code のフックは発火時の情報を JSON でこの入口に流し込む