Claude Codeのフックを使い始めていて、画面に表示される返答の見た目を加工したい人向け
Claudeの返答に混ざる太字記号やコード用の記号を画面だけ消して読みやすくしたいとき、画面共有で鍵情報を伏せたいとき、Agent SDK製アプリで利用者向けに文章を整えたいときに、settings.jsonのMessageDisplay欄に加工スクリプトを登録して使う。返答を止める用途には使えない
MessageDisplay は、Claudeの返答が画面に1行ずつ流れて出てくる、まさにその瞬間に自動で割り込んで「画面の文字だけ」を別の文章に差し替えられるフックです。フックは、決まったタイミングで自分の用意した処理を自動で走らせる仕組みのこと。太字を表す ** やコード用の記号を消して読みやすくしたり、返答に混じった鍵情報を伏せたりできます。
ただ、ここがいちばん大事なところで、差し替わるのは目に見える表示だけ。会話の記録にもClaude本人の頭の中にも、元の文章がそのまま残ります。表示の「化粧」専用、と思っておくと外しません。
噛み砕くと
テレビの生放送に字幕を後乗せするスタッフを想像してください。出演者がしゃべった言葉そのものは録音にしっかり残っています。視聴者の画面に出る字幕だけ、スタッフが「ここの汚い言葉はピー音に」「専門用語はやさしく」と差し替えている。MessageDisplay はこの字幕スタッフです。
出演者(Claude)本人は、自分の言葉が画面でどう書き換えられたか知りません。だから次のセリフは元の言葉を前提に続きます。あくまで視聴者側の見た目を整えるだけ。これがフックの正体です。
大事な前提:このフックは「文章を返すメッセージ」全部で走る
MessageDisplay は誰向けの返答かを絞り込む仕組み、いわゆる matcher を持ちません。Claudeが文章を出すメッセージなら、料理ブログの相談でもエラー調査でも全部で発火します。
逆に、ツールを呼び出すだけで文章ゼロの返答では走りません。ここは公式が明記しています。さらに、公式サンプルを動かすには jq というJSON加工コマンドがインストール済みである必要があります。
「料理ブログ開発中、太字マークやコード用の記号を消す」例で手順を見る
料理ブログを作っている最中、Claudeの返答に **手順** みたいな太字記号や `code` のバッククォートが混ざって読みにくい。これを画面上だけ素の文章にする、という公式サンプルそのままの流れを追います。
ステップ1: フック本体のスクリプトを置く
プロジェクトの .claude/hooks/plain-display.sh に、次の中身を保存します。1文字も変えずにこのまま使えます。
#!/bin/bash
jq '{hookSpecificOutput: {hookEventName: "MessageDisplay", displayContent: (.delta | gsub("\\*\\*"; "") | gsub("`"; ""))}}'
やっていることは単純で、流れてきた文章のかたまり(delta)から ** とバッククォートを消して返すだけ。それを displayContent という箱に入れて返します。
ステップ2: 実行できる状態にする
保存しただけだと「ただのテキスト」なので、走らせる許可を与えます。
$ chmod +x .claude/hooks/plain-display.sh
ここで初心者がやりがちな勘違いがあります。chmod を忘れるとフックが起動せず、画面は何も変わりません。「コードは合ってるのに効かない」の大半はこれです。
ステップ3: 設定ファイルに登録する
プロジェクトの .claude/settings.json、または自分のホームにある同じ名前のファイルに、このフックを呼ぶ宣言を書きます。macOS/Linux ではこの形です。
{
"hooks": {
"MessageDisplay": [
{
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/plain-display.sh",
"args": []
}
]
}
]
}
}
先頭の ${CLAUDE_PROJECT_DIR} は、パソコンが起動時に覚えてる設定値の1つで、いま開いているプロジェクトのフォルダ場所が入ります。これでフォルダがどこにあっても正しく呼べます。
ステップ4: 登録できたか確認する
Claude Codeで /hooks と打つと、いま効いているフックの一覧が出ます。MessageDisplay の欄に plain-display.sh が並んでいれば登録成功です。
ステップ5: 料理ブログの相談をして表示を見る
「魚料理カテゴリの記事構成を考えて」とでも頼んでみます。Claudeが返答を流し始めると、本来 **サバの味噌煮** と出るはずの太字が、画面では素の サバの味噌煮 になって表示されます。記号が消えた読みやすい状態です。
ステップ6: 記録の中身を見比べる
ここが MessageDisplay の肝。会話の記録(transcript)を開くと、そこには **サバの味噌煮** と元の記号付きで残っています。詳細表示モード(verbose)でも元の文章が見えます。画面だけが化粧されていて、裏側は無傷、という二重構造です。
つまり MessageDisplay は何をしてくれるのか
- やってくれる: 画面に出る返答の文字を別の文章に差し替える。記号を消す、鍵情報を伏せる、Agent SDK製アプリで利用者向けに整える、などが代表例
- やってくれない: 返答そのものを止めたり差し止めたりはできない。決定権を一切持たず、記録やClaude本人が見る内容も変えられない
- 意味が薄い場面: ツールを呼ぶだけで文章を返さないメッセージ。そもそも発火しないので何も起きない
使いどころ3シナリオ(具体題材で再現)
シナリオ1: 料理ブログの執筆で記号だらけの返答に疲れたとき
レシピ記事をClaudeと量産していると、画面が ** やバッククォートだらけで目が滑ります。公式サンプルの plain-display.sh をそのまま入れれば、画面表示だけ素の文章になって読み進めやすい。記事に貼り付けるとき用の元データは記録側に記号付きで残っているので、原稿の質は落ちません。表示は読みやすく、データは正確に、を両立できます。
シナリオ2: 家計簿アプリの開発で、返答に鍵情報が混ざるのを画面で伏せたいとき
家計簿アプリの設定をClaudeに相談すると、サンプルとしてAPIの鍵やサーバー名が返答に出ることがあります。MessageDisplay でそれっぽい文字列を伏せ字に差し替えれば、画面録画や画面共有のときに丸見えになりません。ただし後述しますが、伏せ忘れの危険があるので「最後の砦」としては頼り切らないこと。
シナリオ3: Agent SDKで自社アプリにClaudeを組み込み、利用者向けに口調を整えるとき
Agent SDK で作った料理提案アプリを一般ユーザーに出す場合、開発者向けの素っ気ない返答のままだと素人には硬い。MessageDisplay で語尾をやわらげたり前置きを足したりして、利用者に見せる文章を整えられます。この場合、非対話モードなのでメッセージ1通につき1回・全文まとめて渡る挙動になります。
初心者が踏みやすい落とし穴
- 「画面を変えれば記録も変わる」と思い込む。差し替わるのは表示だけ。記録もClaude本人が見る内容も元のまま残り、詳細表示モードでも元が見えます。
- 気に入らない返答をこれで差し止めようとする。MessageDisplay は決定権ゼロ。止める・書き換えて記録する、はできません。終了コード2を返してもブロックできず、元の文章が出るだけです。止めたい対象が「ツールの実行」なら、それは実行前に走って拒否できる PreToolUse フックの役目です。返答の文章そのものを止めるフックはありません。
- 1つのメッセージで1回だけ走ると思って書く。長い返答は完成した行のまとまりごとに何度も呼ばれます。終わりの判定は文章が空かどうかではなく、必ず
finalが true かで見ること。最後のかたまりは中身が空のこともあります。 - 対話モードと非対話モードの違いを見落とす。
claude -pやAgent SDKでは逆に、メッセージ1通につき1回・全文まとめて渡ります。手元の画面で動いたから本番も同じ、とは限りません。 - 重い加工をフックに詰め込む。Claudeはフックが返事するまで表示を止めて待ちます。遅い処理を挟むと返答の表示がカクつきます。制限時間の初期値は10秒で、これは全フックの中でいちばん短い設定です。
- 鍵情報の伏せ字を、これ1本に頼り切る。フックが失敗・時間切れすると元の文章がそのまま出ます。確実に隠したい場面の唯一の防御線にはしないこと。
- message_id を会話記録のIDと突き合わせようとする。これは表示用の識別子で、APIの
msg_…とも記録側のメッセージIDとも別物です。照合には使えません。 - ツール結果や自分が打った文字も加工できると勘違いする。対象はClaudeのメッセージ本文だけ。ツールの実行結果やユーザーの入力はそのまま表示されます。
書き方
// .claude/settings.json に登録する
{
"hooks": {
"MessageDisplay": [
{ "hooks": [ { "type": "command", "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/plain-display.sh", "args": [] } ] }
]
}
}
やってみるとこうなる
入力
#!/bin/bash
jq '{hookSpecificOutput: {hookEventName: "MessageDisplay", displayContent: (.delta | gsub("\\*\\*"; "") | gsub("`"; ""))}}'
出力例
画面表示: Claudeの返答から ** とバッククォートが消え、素の文章で表示される。一方で会話の記録(transcript)と詳細表示モード(verbose)には ** 付きの元の文章がそのまま残る。フックが失敗・時間切れの場合も元の文章が表示される
このページに出てきた言葉
- フック
- Claude Codeの決まったタイミングで自分のスクリプトを自動で走らせる仕組み
- 表示専用(display-only)
- 画面に出る文字だけを変える性質。会話の記録もClaude本人が見る内容も元のまま
- delta
- 今回の表示で新しく出てきた文章のかたまり。返答は完成した行ごとに小出しで渡される
- displayContent
- フックが「画面にはこの文章を出して」と返す箱。返さなければ元の文章が出る
- final
- そのメッセージの最後のかたまりかどうかを示す印。終わり判定はこれで行う
- transcript(トランスクリプト)
- 会話のやり取りを全部ためておく記録。画面を書き換えてもこちらは元のまま
- jq
- JSONの文字列を加工・抜き出しするコマンド。公式サンプルを動かすにはインストール済みが必要
- Agent SDK
- Claudeを自分のアプリに組み込んで動かすための開発キット