<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Claude Code Hooks | けゆきブログ / Keyuki Blog</title>
    <link>https://blog.keyuki.net/tags/claude-code-hooks/</link>
    <description>けゆきブログは、お金・IT・AI活用と暮らしの工夫を、実体験をもとに整理する個人ブログです。</description>
    <language>ja-JP</language>
    <atom:link href="https://blog.keyuki.net/tags/claude-code-hooks/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Claude Codeが画像を開くと止まる。テキスト専用LLMを守るフックを作った</title>
      <link>https://blog.keyuki.net/posts/claude-code-text-only-image-guard/</link>
      <pubDate>Sun, 13 Sep 2026 20:53:24 &#43;0900</pubDate>
      <guid>https://blog.keyuki.net/posts/claude-code-text-only-image-guard/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;https://blog.keyuki.net/posts/claude-code-ollama-vision-model-slot/&#34;&gt;前回の記事&lt;/a&gt;では、Claude Codeが画像を開くと会話が止まる問題を、実際に使っている設定欄を画像対応モデルへ変えることで解決しました。&lt;/p&gt;
&lt;p&gt;では、普段使う文章専用モデルを変えずに、画像が必要な作業を続けるには？今回は、AIが画像を開く直前に止め、画像対応モデルへ解析を任せる方法を作りました。&lt;/p&gt;
&lt;p&gt;原因と対策を先に書くと、こうです。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;使っていたAIモデルは、文章は読めても画像は読めない&lt;/li&gt;
&lt;li&gt;Claude Codeが画像を渡すとエラーになり、その会話では作業を続けられなくなる&lt;/li&gt;
&lt;li&gt;画像を開く直前に止め、画像が読める別のモデルに解析を任せるようにした&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;この記事では、この仕組みを作った過程と、レビューで見つけた落とし穴を紹介します。&lt;/p&gt;
&lt;h2 id=&#34;何が起きたのか&#34;&gt;何が起きたのか&lt;/h2&gt;
&lt;p&gt;私の環境では、Claude Codeを&lt;a href=&#34;https://docs.ollama.com/api/anthropic-compatibility&#34;&gt;Ollama&lt;/a&gt;経由で動かしています。裏側で使う3つのモデルは、どれも文章専用です。Claude Codeの画面は同じでも、接続先のモデルが画像を読めるとは限りません。&lt;/p&gt;
&lt;p&gt;作業中、AIがフォルダ内のPNG画像を&lt;code&gt;Read&lt;/code&gt;という機能で開きました。すると接続先から、次のエラーが返りました。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;this model does not support image input
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;「このモデルは画像入力に対応していない」という意味です。厄介なのは、一度画像が会話に入ると、私の環境では次の指示も同じエラーになること。その会話をそのまま続けられず、&lt;code&gt;/clear&lt;/code&gt;で会話を消してやり直しました。&lt;/p&gt;
&lt;p&gt;原因は、接続先の3モデルがすべて&lt;strong&gt;画像を読めないモデル&lt;/strong&gt;だったことです。Ollamaとの接続部分は画像を受け取れますが、最後に処理するモデルが拒否します。スクリーンショットを会話に直接貼っても同じです。&lt;/p&gt;
&lt;p&gt;画像非対応モデルの見分け方は&lt;a href=&#34;https://blog.keyuki.net/posts/ollama-cloud-claude-code-vision-error/&#34;&gt;以前の記事&lt;/a&gt;で触れました。今回は、そのモデルを使いながらエージェントの画像読み込みを安全に扱う方法がテーマです。&lt;/p&gt;
&lt;p&gt;モデルを選ぶとき、私はコードを書く能力ばかり見ていました。しかしAIは作業の途中で画像も開きます。「文章の性能が高い」だけでは、いつもの作業を安全に続けられるとは限らない。そこが盲点でした。&lt;/p&gt;
&lt;h2 id=&#34;画像を開く前に止め別のaiに渡す&#34;&gt;画像を開く前に止め、別のAIに渡す&lt;/h2&gt;
&lt;p&gt;必要なのは、&lt;strong&gt;画像を読めないモデルに画像を渡さないこと&lt;/strong&gt;です。&lt;/p&gt;
&lt;p&gt;Claude Codeには&lt;a href=&#34;https://code.claude.com/docs/en/hooks-guide&#34;&gt;PreToolUseフック&lt;/a&gt;という、AIがファイルを開く直前に動く仕組みがあります。これを門番にして、&lt;code&gt;Read&lt;/code&gt;の対象が&lt;code&gt;.png&lt;/code&gt;や&lt;code&gt;.jpg&lt;/code&gt;などの画像拡張子なら開くのを拒否します。画像がモデルに届かなければ、今回のエラーは起きません。&lt;/p&gt;
&lt;p&gt;ただし、守れるのは&lt;strong&gt;AIが&lt;code&gt;Read&lt;/code&gt;で画像拡張子のファイルを開く場合だけ&lt;/strong&gt;です。画像の中身まで判定しているわけではないので、拡張子のない画像は通ります。人が会話欄に画像を直接貼る操作も、この門番を通りません。&lt;/p&gt;
&lt;p&gt;止めるだけではAIの作業も止まります。そこで、拒否する理由に&lt;strong&gt;次の手&lt;/strong&gt;を書きました。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;このモデルは画像を読めません。代わりに&lt;code&gt;ollama-vision&lt;/code&gt;で画像を解析してください。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;code&gt;ollama-vision&lt;/code&gt;は、画像を読める別のモデルに1枚だけ渡し、結果を文章で返す自作コマンドです。元のClaude Codeには画像そのものではなく、&lt;strong&gt;解析結果の文章だけ&lt;/strong&gt;が戻ります。&lt;/p&gt;
&lt;p&gt;実際のテストでも、Claude Codeは画像を開くのを拒否されたあと、自分で&lt;code&gt;ollama-vision&lt;/code&gt;を呼び、解析結果を報告しました。会話は止まりませんでした。&lt;strong&gt;拒否の理由に代わりの方法を書くと、AIを安全な経路へ案内できる&lt;/strong&gt;。これが一番の学びです。&lt;/p&gt;
&lt;p&gt;2026年9月14日にも、64×64pxの赤いテスト画像で再試験しました。ガードを外すと画像の&lt;code&gt;Read&lt;/code&gt;で400エラーになり、同じ会話を再開して画像に関係ない指示を出しても再び400でした。ガードを有効にすると&lt;code&gt;Read&lt;/code&gt;が拒否され、代わりのモデルを使って「真っ赤な単色の画像」と答えられました。&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://images.keyuki.net/uploads/2026/09/claude-code-text-only-image-guard-log-no-guard.png&#34;&gt;&lt;img src=&#34;https://images.keyuki.net/uploads/2026/09/claude-code-text-only-image-guard-log-no-guard.png&#34; alt=&#34;ガードなしの再試験。画像のReadでAPI Error 400になり、同じ会話に文字だけで指示しても再び400になるログ画面風の比較画像&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://images.keyuki.net/uploads/2026/09/claude-code-text-only-image-guard-log-guard.png&#34;&gt;&lt;img src=&#34;https://images.keyuki.net/uploads/2026/09/claude-code-text-only-image-guard-log-guard.png&#34; alt=&#34;ガードありの再試験。PreToolUseが画像のReadを拒否し、ollama-visionで解析して回答まで進むログ画面風の比較画像&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;ガードなし／ありの再試験を比較。実行ログの要点を画面風に整えた画像で、端末をそのまま撮ったスクリーンショットではありません。長い引数と個人パスは省略しています。画像をタップすると拡大できます。&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://images.keyuki.net/uploads/2026/09/claude-code-text-only-image-guard-flow.svg&#34;&gt;&lt;img src=&#34;https://images.keyuki.net/uploads/2026/09/claude-code-text-only-image-guard-flow.svg&#34; alt=&#34;対策前は画像のReadがテキスト専用モデルに渡り400エラー。対策後はPreToolUseフックがReadを拒否し、ollama-visionがvisionモデルに解析を委譲してテキスト結果を返す&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;画像拡張子のファイルを&lt;code&gt;Read&lt;/code&gt;する経路を止め、別の画像対応モデルが返した文章だけを使います。図をタップすると拡大できます。&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&#34;作ってから気づいた5つの落とし穴&#34;&gt;作ってから気づいた5つの落とし穴&lt;/h2&gt;
&lt;p&gt;ここからは実装寄りの話です。最初に作ったものをCodexに4回レビューしてもらい、安全装置ならではの落とし穴を直しました。&lt;/p&gt;
&lt;h3 id=&#34;1-門番が壊れたら通さず止める&#34;&gt;1. 門番が壊れたら、通さず止める&lt;/h3&gt;
&lt;p&gt;フックのプログラムが&lt;code&gt;exit 1&lt;/code&gt;などで終わると、Claude Codeはエラーを知らせつつもファイルを開く処理を続けます。これでは画像がすり抜けます。&lt;/p&gt;
&lt;p&gt;そこで、門番に異常があれば&lt;code&gt;Read&lt;/code&gt;全体を止めるようにしました。具体的には、Claude Codeがブロックとして扱う終了コード&lt;code&gt;2&lt;/code&gt;を返します。安全側に倒す、いわゆる「fail-closed」です。&lt;/p&gt;
&lt;h3 id=&#34;2-入力の欠落型違いを見逃さない&#34;&gt;2. 入力の欠落・型違いを見逃さない&lt;/h3&gt;
&lt;p&gt;フックに渡される情報が空だったり、ファイル名の項目が欠けていたり、文字列ではなかったりした場合は止めます。そこで「画像ではない」と決めつけて通すと、Claude Code側の仕様が変わったときに、守れなくなったことに気づけないからです。&lt;/p&gt;
&lt;p&gt;ただし、ファイル名が空文字の場合は現在の実装では通ります。入力検証が万全というわけではありません。&lt;/p&gt;
&lt;h3 id=&#34;3-設定が上書きされないようにする&#34;&gt;3. 設定が上書きされないようにする&lt;/h3&gt;
&lt;p&gt;私の起動環境では、&lt;code&gt;--settings&lt;/code&gt;で設定を複数渡すと&lt;strong&gt;最後の指定が優先&lt;/strong&gt;されました。後から別の設定を渡すと、画像を止めるフックが消えてしまいます。&lt;/p&gt;
&lt;p&gt;そこで、ガードが有効な間は、別の&lt;code&gt;--settings&lt;/code&gt;が指定されたら起動を止めて理由を表示します。&lt;/p&gt;
&lt;h3 id=&#34;4-大きな画像をコマンド引数に入れない&#34;&gt;4. 大きな画像をコマンド引数に入れない&lt;/h3&gt;
&lt;p&gt;画像をテキストに変換して、コマンドの引数として渡す作りにしたら、867KBのPNGで失敗しました。引数にはサイズの上限があったためです。&lt;/p&gt;
&lt;p&gt;引数ではなく標準入力（コマンドに流し込むデータ）で渡す形に変えたところ、4.2MBの画像でも動きました。実装では&lt;code&gt;jq -Rs&lt;/code&gt;と&lt;code&gt;curl --data-binary @-&lt;/code&gt;を使っています。&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
