「同じ前提を、毎回いちばん最初に貼り直している」。AIエージェントに仕事を任せていると、この作業に行き当たります。
Claude Codeのセッションは、起動・再開・/clear・圧縮・forkのたびに区切られます。区切られた向こう側に、前の会話はありません。読ませたはずの運用ルールも、そこで一度切れます。
この記事は、その切れ目のたびに必読ルールを自動で読み込ませるSessionStartフックを扱います。素材は実際に動いているPythonのコードと設定ファイル、そして公式ドキュメントです。
検証環境:claude-opus-5 / Claude Code v2.1.x / macOS 15 / 2026-08-02検証。
こんなふうに調べていませんか
- 運用ルールを書いたのに、セッションが変わると読まれていない
- 起動のたびに同じ前提を貼り直していて、そこから抜けたい
- フックという言葉は聞くが、どこに何を置けばいいのか分からない
この記事を読み終えたときに手に入るもの
- セッションの切れ目でファイルを読ませ直す仕組みを、自分の環境に置けるようになります
- 自動で読まれるファイルと、頼まないと読まれないファイルを線引きできるようになります
- 書いたフックが効いているかどうかを、その場で確かめられるようになります
結論30秒でわかる、この記事の結論
- SessionStartフックは、セッションが始まるたびに1回だけ発火し、スクリプトの標準出力をそのままClaudeの文脈へ渡す仕組みです。
- 自動で読まれるのはCLAUDE.md系のファイルだけで、館内マップやルール索引はその外側にあります。
- 実行を止める力はありません。渡すところまでが役割で、渡した先の判断はモデルに残ります。
進行役は3人です。若葉さんが用語のところから聞き、高梨課長が自分の手で動かす側から聞き、鈴木さん(本誌監修)が答えます。
01SessionStartフックって、AIエージェントに何を毎回読ませる仕組みなんですか?
若葉さんフックという言葉が、まだぼんやりしています。何が起きる仕組みなんでしょうか。
鈴木さん会議室の入口に立って、入ってきた人へ同じ資料の束を手渡す係だと思っています。覚えているはずだ、を当てにしない。そこがこの仕組みの考え方です。
SessionStartフックとは、セッションが始まるたびに1回だけ発火し、スクリプトの標準出力をClaudeの文脈へ注入できる仕組みです。
渡るのは、終了コード0で終わったときの標準出力です。公式ドキュメントによれば、標準出力は通常デバッグログ止まりで文脈には渡りません。SessionStartは、その数少ない例外の1つです(出典: Claude Code公式)。
もう1つの特徴は、判定を挟まず常に発火することです。実行してよいかを判定するPreToolUseとは、ここが最も違います。実行そのものを止める判断はClaude CodeのPreToolUseで危険コマンドを遮断する4つの判定が扱う役割で、SessionStartにその機能はありません。イベントの種類と選び方はClaude Code hooksの一覧|30種類から選ぶ2つの軸にまとめています。
順番に意味があります。フックが走るのは、こちらが最初の1行を打つより前です。だから「先に読んでおいてください」と頼む必要がありません。頼む相手が、もう読んだ状態で座っている。そういう状態を作れます。
02AIエージェントが自動で読むファイルと、SessionStartフックで足すファイルはどう違うんですか?
Claude Codeは、CLAUDE.md・CLAUDE.local.mdと、.claude/rules/のpaths指定なしファイルを毎回自動で読み込みます。paths指定があるファイルは、該当パターンのファイルを開いたときにだけ読み込まれます。プロジェクト直下のCLAUDE.mdは/compactのあとも自動で再読み込みされます(出典: Claude Code公式)。
問題になるのは、その外側です。館内マップやルール索引のように「必ず目を通してほしい」と決めているのに、自動読み込みの対象になっていないファイルがあります。ここが空くと、規約は書いてあるのに読まれていない、という状態が生まれます。SessionStartフックは、この空きを埋めるために使います。
分けて並べると、ルールを守らせる話が、実は置き場所の話だと分かります。同じ内容を書いても、置いた場所によって読まれるかどうかが変わります。文面を練り直す前に、そのファイルがどちら側にいるのかを確かめます。
この章のまとめ
自動で読まれる範囲はCLAUDE.md系で決まっています。それ以外を読ませたいときに、SessionStartが要ります。
03SessionStartフックは、AI社員のセッションがどう区切られたときに発火するんですか?
若葉さんそもそも、セッションが区切られるというのは、何が起きているんですか。
鈴木さん前の会話を当てにできなくなった、ということですね。席を立って戻ってきたのか、机の上を空にしたのかは違いますが、こちらから見ると同じ扱いになります。
発火するsourceは5パターンです(出典: Claude Code公式)。
startup:新規セッションを開いたときresume:--resume・--continue・/resumeで会話へ戻ったときclear:/clearで会話を空にしたときcompact:自動または手動でコンテキストを圧縮したときfork:/fork・/branch、または--resume・--continueに--fork-sessionを付けて会話を複製したとき
置きかえてみると、5つが同じことの言い換えだと分かります。どれも「前の会話を当てにできなくなった瞬間」です。だから、どの発火源を選ぶかで悩む場面はほとんどありません。
forkの扱いだけ、以前は違いました。v2.1.214より前は、forkしたセッションのsourceもresumeのまま報告されていました(出典: Claude Code公式)。
04SessionStartがAIエージェントへ返せる5つのフィールドは、何がどう変わるんですか?
公式ドキュメントによると、SessionStartがhookSpecificOutputで返せるフィールドは5つです。
| フィールド | 型 | 効果 |
|---|---|---|
| additionalContext | 文字列 | 最初のプロンプトより前に文脈を注入する |
| initialUserMessage | 文字列 | -pモード(非対話実行)でセッション最初のユーザー発言として使う。プロンプトを渡していても、initialUserMessageが1ターン目になり渡したプロンプトは2ターン目として続く |
| watchPaths | 文字列の配列 | 指定したファイルの変化をFileChangedイベントで監視する |
| sessionTitle | 文字列 | UIに表示するセッション名を設定する |
| reloadSkills | 真偽値 | trueにするとSessionStartフック完了後にスキルとカスタムコマンドのディレクトリを再スキャンする |
additionalContextとinitialUserMessageは、似ているようで置き場所が違います。前者は会話の外側に足すもの、後者は会話そのものの1つ目になるものです。後者を使うと、渡したはずのプロンプトが後ろへ回ります。
本記事は必読ルールの注入に使うadditionalContextを中心に扱います。スキル更新後の再読み込みに使うreloadSkillsはフィールドの1つとして紹介するに留め、使い方は別記事に譲ります。前提の整え方はClaude Codeの初期設定|AIエージェントに任せる前の4観点でまとめています。
05起動時フックを動かすには、AI導入の現場で何を用意すればいいんですか?
高梨課長自分の環境で動かしてみたいのですが、権限まわりで止まりそうで手が出ません。
鈴木さんそこは拍子抜けするほど何も要りません。SessionStartは止める仕組みではないので、許可を出す・出さないという話にそもそもなりません。
SessionStartはブロックしない仕組みなので、permissionsのallow・ask・denyを設定する対象になりません。
必要なのは次の3つです。
- 書き込み権限のあるsettings.json
- スクリプトを実行できるインタプリタ(今回はpython3)
- どのスコープに置くかの判断
| スコープ | 置き場所 | Git共有 | 適用範囲 |
|---|---|---|---|
| Managed | 組織が配布する管理ファイル | 管理者のみ変更可 | 組織全体・最優先 |
| User | ~/.claude/settings.json | 不可 | この端末の全プロジェクト |
| Project | .claude/settings.json | 可(コミット対象) | このリポジトリを開く全員 |
| Local | .claude/settings.local.json | 不可(gitignore対象) | 自分のこの環境だけ |
2軸に置くと、選ぶ基準が1つに絞れます。同じものを他の人にも読ませたいかどうかです。読ませたいならGitに乗る側、自分の手元だけで試すなら乗らない側を選びます。
必読ルールをチームやAI社員全員に同じように読ませたいならProjectを選びます。WEBMARKSは.claude/settings.jsonにSessionStartフックを3本登録しています(2026-08-02に設定ファイルを直接確認)。リポジトリを開いた全員(人間・AIを問わず)に同じ内容を注入する設計です。
公式サンプルは${CLAUDE_PROJECT_DIR}という環境変数をコマンド内で使います。harnessがフック実行時にルートパスを渡すため、フルパスをハードコードせずに済みます。
この章のまとめ
置き場所の判断は、Gitで配るかどうかで決まります。全員に同じものを読ませるならProjectです。
06必読ファイルをAIエージェントへ渡すSessionStartフックのスクリプトは、どう書くんですか?
入力は「注入したいファイルのパス一覧」、出力は標準出力へのテキストで、それだけで足ります。reloadSkillsのような追加フィールドが要らないなら、JSONを組み立てずprint()するだけで動きます。
#!/usr/bin/env python3
"""起動時必読ファイル自動注入 — SessionStart フック(要点抜粋)"""
import os
import sys
# 全文注入する固定ファイル(プロジェクトルートからの相対パス)
INJECT_FULL = [
("MEMORY.md", "部署/AI社員/ルーティングの正本"),
("00-rules/_INDEX.md", "横断運用ルールの入口"),
]
def project_dir():
d = os.environ.get("CLAUDE_PROJECT_DIR")
if d and os.path.isdir(d):
return d
# harnessがCLAUDE_PROJECT_DIRを渡さない場合のフォールバック
return os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
def read_file(path):
try:
with open(path, encoding="utf-8") as f:
return f.read().strip()
except Exception:
return None # 1ファイルの失敗で全体を止めない
def main():
root = project_dir()
out = ["起動時必読ファイル(SessionStart 自動注入)"]
for rel, desc in INJECT_FULL:
content = read_file(os.path.join(root, rel))
out.append(f"■ {rel} — {desc}")
out.append(content if content is not None else f"見つからない: {rel}")
print("\n".join(out))
if __name__ == "__main__":
try:
main()
except Exception:
pass
sys.exit(0) # SessionStartはブロックできないので常に0で終えるここで言うMEMORY.mdは、Claude Code自身が学習内容を書き込む自動メモリとは別物です。自動メモリの実体は~/.claude/projects/<project>/memory/MEMORY.mdです。プロジェクト直下のMEMORY.mdとは名前が同じだけの別ファイルです。
07このフックを書くとき、AIエージェントの運用のために外せない3点は何ですか?
設計判断は3点です。
- 標準ライブラリのみで書く:
osとsysだけなら実行環境を選びません。 - CLAUDE.mdは再注入しない:WEBMARKSは
AGENTS.mdをCLAUDE.mdへのsymlinkにしています(2026-08-02にls -laで実測)。公式ドキュメントの記載どおりClaude CodeはCLAUDE.mdだけを自動で読み込み、AGENTS.mdは読みません(出典: Claude Code公式)。symlinkによってAGENTS.mdを見る他のツールにも同じ内容が届くだけなので、SessionStartフックでCLAUDE.md自体を再注入する必要はありません。書き方自体はCLAUDE.mdの書き方|7章テンプレートと200行の分け方にまとめています。 read_file()はファイルごとに例外を握りつぶす:理由は後半のつまずきで説明します。
確認方法は、このスクリプトを単体で直接実行することです。標準入力を与えずpython3 inject-startup-reading.pyと打ちます。MEMORY.mdと00-rules/_INDEX.mdの中身が、そのまま標準出力に流れます。
08書いたフックをsettings.jsonへ登録するとき、AI活用の現場で何を間違えやすいんですか?
書いたスクリプトを.claude/settings.jsonのhooks.SessionStartに登録します。
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/inject-startup-reading.py",
"statusMessage": "起動時必読ファイルを読み込み中…"
}
]
}
]
}
}matcherを書いていない点が要です。公式サンプルは"matcher": "startup"のように発火源を絞りますが、絞ると新規起動以外では発火しません。省略すると5パターンすべてで発火します。
typeにも制約があります。全hooksが対応する実行方式はcommand・http・mcp_tool・prompt・agentの5種類です。このうちSessionStartが対応するのはcommandとmcp_toolの2種類だけです。promptとagentは非対応と公式ドキュメントに明記されています(出典: Claude Code公式)。
09フックの終了コードで、AIエージェントに届くものはどう変わるんですか?
| 終了コード | SessionStartでの挙動 |
|---|---|
| 0 | 標準出力がそのままClaudeの文脈として採用される(ほとんどのイベントではデバッグログ止まりだが、SessionStartは例外) |
| 2・それ以外(0以外はすべて同じ扱い) | <hook名> hook errorという通知とstderrの1行目が人間にだけ表示され、Claudeには渡らずセッションは続行する(出典: Claude Code公式) |
分かれているのは、届く相手です。片側ではモデルに届き、もう片側では人の画面に残るだけになります。同じ文字列を印字していても、終了コードが違えば読む相手が変わります。
確認方法は/hooksと入力することです。登録済みのSessionStartエントリとcommandのパスが読み取り専用で一覧表示されます。
10SessionStartフックを3本並べると、AIエージェントの起動時に何が起きるんですか?
公式ドキュメントは、hooksがスコープをまたいで「上書きではなくマージされる」と明記しています。同じイベントに複数の登録があれば、それぞれが実行されるという意味です(出典: Claude Code公式)。
WEBMARKSの本番設定は、この性質を使ってSessionStartに3本のフックを別々のオブジェクトとして並べています(2026-08-02に設定ファイルを直接確認)。
| スクリプト | 出力条件 | 役割 |
|---|---|---|
| inject-startup-reading.py | 常に出力 | 必読ファイル2件をそのまま注入する |
| check-unfinished-tasks.py | 台帳の状態に応じて出力 | 未完了タスクがあれば要約を注入する |
| check-loop-health.py | 異常時のみ出力 | 運用ルールの構造違反があるときだけ警告する |
11フックを役割ごとに分けると、AI社員の起動時に何が守られるんですか?
高梨課長1つのスクリプトに全部書いてしまうのと、分けて置くのとでは、何が変わりますか。
鈴木さん転んだときに残る範囲が変わります。分けてあれば、転んだ1本だけを直せます。まとめてあると、どこで転んだのかを探すところから始まります。
2本目は、条件分岐と例外処理を両方持つ例です。
def main():
root = project_dir()
ledger = os.path.join(root, LEDGER_REL)
if not os.path.isfile(ledger):
return 0
try:
validation_error, parse_ledger, render_summary = load_governance(root)
with open(ledger, encoding="utf-8") as handle:
tasks = parse_ledger(handle.read())
print(render_summary(tasks))
except Exception as exc:
if validation_error and isinstance(exc, validation_error):
print(f"タスク台帳エラー: {exc}")
else:
print(f"タスク継続チェック判定不能: {exc}")
return 0台帳ファイルが無ければ何も印字せず終了します。あれば読み込みを試み、構造エラー(LedgerValidationError)とそれ以外の例外でメッセージを分けます。3本目はさらに踏み込み、外部ツールをサブプロセスで15秒のタイムアウトつきで呼び出し、構造違反が0件なら無言で終わります。
分けておく理由は、行数ではありません。転んだときに巻き添えになる範囲です。3本のうち1本が失敗しても、他の2本とセッション開始自体には影響しません。
この章のまとめ
役割ごとに分けると、1本の失敗が全体を道連れにしません。分ける単位は「出力条件」で切ると迷いません。
12生成AIの運用でつまずくのは、SessionStartフックのどこなんですか?
高梨課長書いたつもりで効いていない、という話をよく聞きます。どこで外すのでしょうか。
鈴木さん3つに集まります。止まると思い込む、出力が丸ごと消える、発火源を絞りすぎる。どれも画面上は何も起きないので、気づきにくいところが共通しています。
終了コード2で止められると思い込む
PreToolUseの感覚で「危険なら終了コード2を返せば止まる」と考えると外れます。SessionStartは終了コード2でも標準エラー出力を人間にしか見せません。Claudeの文脈には渡らず、セッションも止まりません。ブロックする設計を検討している時点で、そのイベントがSessionStartではない可能性を疑ってください。
1つの例外で全部の出力が消える
複数ファイルを1つのtryで囲むと、最初の1件で例外が起きた時点でスクリプトが止まり、残りのファイルの内容が丸ごと印字されません。しかも終了コードが0以外になれば、その出力自体がClaudeに渡らず、画面には「hook error」としか残りません。read_file()をファイルごとに独立させ、失敗したファイルだけ1行の代替メッセージに置き換えるのは、この壊れ方を防ぐためです。
matcherを絞りすぎて、/clear後に再注入されない
公式サンプルをそのまま真似て"matcher": "startup"と書くと、新規起動時にしか発火しません。/clearは会話履歴を空にして新しい会話を始めるコマンドで、直後にもSessionStartは発火しますが、matcherがstartupに絞られていれば無視されます。必読ルールが一番必要なのは会話が空になった直後なので、matcherは省略し、5パターン全部で発火させます。
比べると、絞ることで手放すのが「新規起動以外の全部」だと分かります。いちばん読ませたい場面が、いちばん先に落ちます。
13SessionStartフックが効いたかどうかは、AIエージェントの何を見れば分かるんですか?
判定基準は4つです。
/hooksにSessionStartの登録本数(今回は3本)とcommandのパスが表示されている- スクリプトを直接実行し、標準出力に必読ファイルの内容がそのまま出る
- 新規セッションを開始した直後、最初の返信より前に注入内容が文脈に乗っている
/clearを実行した直後にも、同じ内容が再び注入される
順番にも意味があります。手前から見ていくと、外れたときに原因が1か所へ絞れます。いきなり最後だけを見ると、登録の問題なのかスクリプトの問題なのかが分かりません。
4つ目が一番見落とされます。新規起動時だけ確認して満足すると、matcherを絞る誤りに気づけません。本記事の執筆環境でも、/clear直後にSessionStartが発火することを確認しています。MEMORY.mdと00-rules/_INDEX.mdの内容が再び文脈に乗ることも、2026-08-02に確認済みです。
14よくある質問
SessionStartフックだけで、AIが規約を守ることは保証されますか
保証されません。SessionStartが保証するのは「文脈に情報が乗ること」までです。乗った情報をどう扱うかはモデルの判断に残ります。実行を止める判断が要るなら、PreToolUseのようなブロック可能なイベントと組み合わせます。読ませることと守らせることは、別々に設計します。
SessionStartとUserPromptSubmitはどちらを使うべきですか
セッションが始まった瞬間に1回だけ注入したいならSessionStartです。ユーザーが送るプロンプトのたびに毎回チェックしたいならUserPromptSubmitを使います。発火頻度が根本的に違います。毎回発火する側に長い文章を積むと、そのぶん文脈を使い続けることになるので、頻度から先に決めます。
既存のCLAUDE.mdと、SessionStartで注入する内容はどう役割分担しますか
CLAUDE.md自体はプロジェクト指示として自動的に読み込まれているので、SessionStartで同じ内容を重ねて印字する必要はありません。CLAUDE.mdに書ききれない動的な情報や、CLAUDE.md以外の必読ファイルをSessionStartに任せるのが役割分担です。重ねて印字しても、同じ文章が二重に載るだけになります。
SessionStartを使うのに、permissionsで許可を出す必要はありますか
要りません。SessionStartはブロックしない仕組みなので、permissionsのallow・ask・denyを設定する対象になりません。許可の設計が要るのは、実行の可否を判定する側のイベントです。用意するのは、書き込める設定ファイルと、スクリプトを動かせるインタプリタと、どのスコープに置くかの判断だけです。
フックを3本並べたとき、1本が失敗すると他も止まりますか
止まりません。同じイベントに複数の登録があれば、それぞれが実行されます。3本のうち1本が失敗しても、他の2本とセッション開始自体には影響しません。ただし失敗した1本の出力は届かないので、静かに欠けます。欠けても画面は何も言わないため、登録したあとに出力が乗っているかを見に行く手間は残ります。
15まとめ|今日やる3つのこと
自動で読まれる範囲はCLAUDE.md系で決まっていました。その外側を読ませ直すのがSessionStartで、渡せるのは終了コード0で終わった標準出力だけでした。そして、書いたフックは発火させて出力を見るまで、効いていないものとして数えます。
今日はこの順に手を動かします
自動で読まれているファイルと、読まれていない必読ファイルを書き出す
どちら側にあるかで、打つ手が変わります
注入スクリプトを単体で走らせて、標準出力に中身が出るところまで見る
登録の前に切り分けを済ませます
matcherを書かずに登録して、/clearの直後にもう一度出るかを見る一番読ませたい場面から確かめます
AI検索では、こう聞かれています
起動のたびに必読ルールを読ませるには、どうすればいいんですか?
「SessionStartフックって、AIエージェントに何を毎回読ませる仕組みなんですか?」の章で説明しています
CLAUDE.md以外のファイルは自動で読まれないんですか?
「AIエージェントが自動で読むファイルと、SessionStartフックで足すファイルはどう違うんですか?」の章で線を引いています
SessionStartフックはどこに書けばいいんですか?
「起動時フックを動かすには、AI導入の現場で何を用意すればいいんですか?」の章で置き場所を扱っています
書いたフックが効いているかは、どこで確かめられますか?
「SessionStartフックが効いたかどうかは、AIエージェントの何を見れば分かるんですか?」の章に判定基準があります
次に読むなら、この記事です