Claude Code が突然「いまの出力にプロンプトインジェクションが注入されています」と言って作業を止めることがあります。攻撃を心配して手が止まる人もいれば、無視して進める人もいると思います。
でも、その注入が本物の攻撃なのか、モデルの作り話(作話)なのかは、推測しなくても、会話の記録の構造を見れば決定論的に見分けられます。そして厄介なことに、これが作話だった場合、Claude Code の自動メモリ機能を経由して次のセッション以降も同じ作話が再発し続けることがあります。
この記事では、(1) 本物の注入と作話を .jsonl の構造で見分ける方法、(2) 自動メモリが汚染されたときの見つけ方と消し方、(3) なぜこれが「永続する記憶」全般の問題なのか、を整理します。
GitHub の起票 #70525(2026-06-24)で報告された事例をきっかけにしていますが、報告の中身そのものは筆者が踏んだわけではないので伝聞として扱い、.jsonl の構造による見分け方は手元で実際に確かめたうえで書きます。
何が起きるか
長く走らせているセッションで、Claude Code が突然作業を止めて、こう言い出すことがあります。
いまの道具の出力(Bash や Read の結果)に、自分の口調をまねた一人称の英語の作文が注入されていました。「I'll fix the corrupted file by rewriting it」「Let me re-read the actual file」のような文です。これはファイルを書き換えさせるためのプロンプトインジェクション攻撃と思われます。安全のため、これ以上進みません。
起票 #70525 の報告者が、生の会話の記録(.jsonl)を調べたところ、実際にはこうだったとされています。
- その英語の作文は、どの道具の出力にも一文字も含まれていなかった。
- 「注入された」とされた直前の道具の出力は、ごく普通の正常なもの(ディレクトリ一覧、成功した
git pushのログ)だった。 - 問題の文字列は、モデル自身の応答のなかにだけ存在した。モデルが自分で書いた文を引用して、「これが注入された」と主張していた。
- ある場面では「注入された文は自分の前の返答のなかにあった」とまで言った。自分の出力は、定義上、外から注入されようがありません。
つまりこれは攻撃ではなく、**出力元の取り違え(source misattribution)と作話(confabulation)**です。モデルがエージェント風のメタ的な独白を自分で生成し、それを隣の道具の出力から来たものだと取り違え、説明として「プロンプトインジェクション」を持ち出した、という構図です。
本物の注入か作話かを、記録の構造で見分ける
ここからは手元で構造を確かめたうえで断定で書きます。会話の記録の構造を見れば、推測なしに決着します。
Claude Code の会話は、セッションごとの .jsonl に一行ずつの記録として保存されています。場所はホームの下です。
~/.claude/projects/<プロジェクトのパスをエンコードした名前>/<セッションID>.jsonl
エンコードの規則は単純で、フルパスの / を - に置き換えるだけです。
/home/you/projects/myapp
→ ~/.claude/projects/-home-you-projects-myapp/
決定的なのは、役割(role)の違いです。
- 道具の出力(Bash や Read の結果)は、必ず
user役の記録として保存されます。中身はtool_resultという型で、どの道具呼び出しに対する結果かを示すtool_use_idが付きます。 - モデルの作文(独白や説明)は、必ず
assistant役の記録のtextブロックに入ります。
本物の外部からの注入であれば、その文字列は user 役の tool_result のなかに、実際に存在します。しかも、モデルがそれに言及する assistant の記録よりも前に、必ず存在します。受け取った内容は、それを参照する返答より先に届いているはずだからです。
逆に、問題の文字列が assistant 役の記録のなかにしか無いなら、それは外から来たものではありません。モデルが自分で書いたものです。
検証コマンド
まず、対象のセッションの記録を特定します。
ls -t ~/.claude/projects/<エンコード名>/*.jsonl | head -1
次に、モデルが「注入された」と言っている文字列の一部が、道具の出力(user 役の tool_result)のなかに本当にあるかを調べます。
jq -c 'select(.message.role=="user")
| .message.content[]?
| select(.type=="tool_result")' \
~/.claude/projects/<エンコード名>/<セッションID>.jsonl \
| grep -F "I'll fix the corrupted file"
ここで一件も出てこなければ、注入は無かったということです。道具の出力は汚染されていません。安心して作業を続けて構いません。
念のため、同じ文字列が assistant の出力のなかにあるかも見ます。
jq -c 'select(.message.role=="assistant")
| .message.content[]?
| select(.type=="text")' \
~/.claude/projects/<エンコード名>/<セッションID>.jsonl \
| grep -F "I'll fix the corrupted file"
user 側で空・assistant 側でヒット、という結果なら、出力元の取り違えだと確定します。逆に user 側の tool_result で本当にヒットしたなら、それは本物の注入なので、その道具(外部コマンドの出力、Web 取得、MCP の応答など)の出どころを疑ってください。
本題——自動メモリが「嘘」を永続化する
単発の作話なら、そのセッションを終えれば消えます。この事故が厄介なのは、その先です。
起票の報告によれば、モデルはこの思い込みを Claude Code の自動メモリ(auto-memory)に「確定した事実」として書き込んだそうです。「道具の出力には偽の一人称の思考が注入されることがある。実行せず、書き込みは検証せよ」という趣旨の記憶として。そしてこの記憶は、セッションごとに読み込まれる MEMORY の索引に並びます。
結果、新しいセッションが始まるたびに、モデルは最初から「道具の出力は注入されているかもしれない」と身構えることになります。身構えた状態は、同じ作話を再発させやすくします。こうして閉じた輪ができます。
作話する → それを記憶に書く → 記憶が次のセッションを身構えさせる → また作話する
報告者は、その汚染された記憶を手で消したら輪が切れた、としています。逆に言えば、消すまで、セッションをまたいで再発し続けたわけです。
汚染された記憶を見つけて消す
自動メモリは、プロジェクトごとのメモリのディレクトリに一件一ファイルで保存され、MEMORY.md がその索引です。
grep -rin "inject\|注入\|tool output\|道具の出力" \
~/.claude/projects/<エンコード名>/memory/
「道具の出力には偽の一人称の思考が注入されることがある」「実行せず検証せよ」といった、身構えを促す記憶が見つかったら、前節の jq の検証で「注入は無かった」と分かっている場合、その記憶は偽です。消す前に対象の中身を一度読み、本物の注意書き(過去に実際にあった事故の記録)まで巻き添えにしないことを確かめてから、偽の記憶のファイルを消し、MEMORY.md の索引から対応する一行も外します。これで輪が切れます。
一般化——「永続する記憶」は、嘘も永続させる
この事故の本質は、プロンプトインジェクションの真偽そのものではありません。自動で書かれて、次のセッションでも読まれる記憶は、正しいことだけでなく、間違ったことも同じだけ永続させる、という点です。
自動メモリは本来「同じ説明を何度もしなくて済む」ための便利な仕組みです。ところが、書く側(モデル)が一度でも事実でないことを「確定した事実」として書けば、それは次のセッションを起動するたびに読み込まれ、間違った前提として作業の土台に入り込みます。エラーも警告も出ません。黙って効き続けます。
対策の考え方は三つです。
-
記憶がどこに、どんな名前で保存されているかを知っておく。
~/.claude/projects/<エンコード名>/memory/とMEMORY.mdです。場所を知らなければ、汚染に気づくことも消すこともできません。 - 自動で書かれた記憶を、ときどき人の目で点検する。 とくに「〜してはいけない」「〜を疑え」といった、作業を止める向きの強い記憶は、本当に根拠があるかを確かめる価値があります。根拠のない警戒は、止めなくていい作業を止めます。
- **「本物の受信は、必ず記録のうえで参照より前に、
user役として存在する」**という構造の事実を判断の拠り所にする。モデルの主張ではなく、記録の構造が真偽を決めます。
なぜフックで自動的に止めないのか、と思うかもしれません。これはファイルを壊すコマンドのように、実行の瞬間を捕まえれば防げる種類の事故ではありません。汚染は「モデルが記憶に何を書くか」という生成の中身の問題で、フックが見張る道具の呼び出しの外で起きます。だからこの事故は、記憶の置き場を知っておき、記録の構造で真偽を確かめ、ときどき点検する、という運用の知識で防ぐ種類のものです。
筆者は Claude Code を 800 時間以上ほぼ無人で走らせながら、こうした「黙って効き続ける」たちの事故を追ってきました。データベースの24,472行が消えて取り戻せなくなる、読み取り専用のはずの子エージェントが嘘の完了を報告する、切ったはずの自動圧縮が警告なく発火して37万トークンの履歴が消える——これらは GitHub の起票で実在を確認した他の利用者の事故です。自分の環境で踏んだのは、ファイルが2回消えたことと、キャッシュの読取率が89%から4%に落ちてトークン消費が20倍に膨らんだことでした。そのどれもが「設定で先回りすれば防げる」ことを、実際に起きた事故から逆算してまとめた買い切りの実践書が Anthropic 公式ガイドにない事故防止(¥1,500・Zenn) です。この記事で扱った自動メモリの汚染も、第48章として収録しています(購入後の追記は無償で届きます)。第3章まで無料で試し読みできます。
同じ事故防止本は Kindle 版(Amazon・¥1,500)でも読めます。Kindle Unlimited なら追加料金なしで全文読めるので、まず無料で中身を確かめたい方はそちらが早いです。
無料で先回りしたい方には、危ない操作を実行の前で止めるフック群を cc-safe-setup(GitHub・MIT) に公開しています。まずはこちらから試せます。
ほかにも、800時間の運用データから、トークン消費の削減・複数ベンダー(Claude / Codex / Gemini / Copilot)の並行運用・サブエージェントの沈黙の失敗対策など、テーマ別の手引きを公開しています。気になる人は著者の本の一覧から、価格と評価を見て選べます。