auto modeで「a hook changed this call's input」と拒否される理由と対処
「a hook changed this call's input after the model wrote it」は、分類器の審査後にhookが入力を書き換えたときの拒否です。原因の絞り方と、続くときの選択肢をまとめます。
auto modeでツール呼び出しが a hook changed this call's input after the model wrote it という理由で拒否されたら、原因は「審査のあとに、どこかのhookが入力を書き換えた」ことです。書き換えたhookは、modの tool.call か turn.step、または設定ファイルの PreToolUse のどれかですが、メッセージはそのどれかを教えてくれません。
同じ呼び出しをもう一度出させても拒否が続くなら、そのhookは毎回入力を変えています。止めるのはhookかmodの側です。
なぜ「書き換え」だけが拒否されるのか
auto modeでは、サーバー側の分類器(classifier)がツール呼び出しを審査します。審査の対象は、モデルが書いた入力です。審査が終わったあとにhookが入力を変えると、実際に走る内容は審査を通ったものと別物になります。
そこでClaude Codeは、審査が及ばないその呼び出しを拒否します。拒否の理由として返るのが、冒頭の一文です。
分類器に回るのは、権限ルールで決まらなかった呼び出しです。読み取りだけの操作や、作業ディレクトリ内のファイル編集は自動で承認され、分類器を通りません。サーバー側の審査があるセッションでは、読み取り専用やサンドボックス内のシェルコマンドも、その審査を待ちます。このメッセージがBashのような特定のツールで出やすいのは、そのためと考えられます。
拒否までの流れ
- 1
モデルが入力を書く
たとえばBashのコマンド文字列を組み立てます。
- 2
サーバー側の分類器が審査する
審査が見たのは、この時点の入力です。
- 3
hookが入力を変える
modの
tool.callかturn.step、またはPreToolUsehookが該当します。 - 4
Claude Codeが拒否する
実行内容が審査済みのものと違うため、
a hook changed this call's input after the model wrote itを理由に拒否します。
この拒否はauto modeの中で出るものです。サーバー側の分類器による審査の仕組みは、auto modeの分類器は何を止めるかにまとめています。審査をサーバーに任せるかどうかは CLAUDE_CODE_AUTO_MODE_SERVER で切り替えられます(詳しくはこちら)。
書き換えているhookを突き止める
候補は3つあります。
| 候補 | 書き換えの入口 | 置き場所 |
|---|---|---|
modの tool.call hook | 書き換えの入口ツールが実行される直前に入力を変える | 置き場所modのモジュール |
modの turn.step hook | 書き換えの入口モデルへの1回のリクエストの直前で動く | 置き場所modのモジュール |
PreToolUse hook | 書き換えの入口updatedInput で入力全体を置き換える | 置き場所設定ファイルのhooks |
PreToolUse の updatedInput は、ツールの入力オブジェクト全体を置き換えます。変えていない項目も含めて返す決まりです。例えば次のような形の出力が、これに当たります(公式の説明に沿った形の例です)。
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow",
"updatedInput": {
"command": "npm test -- --silent"
}
}
}Bashのコマンドをhookが npm test -- --silent に差し替える設定があれば、auto modeではモデルが書いた文字列と実行される文字列がずれます。ずれた時点が、このメッセージの出番です。
modを洗い出す
modのどれが tool.call や turn.step を持っているかは、デバッグログで見ます。--debug-file でログの出力先を決めて起動します。
claude --debug-file ./debug.log別のターミナルでログを追います。読み込まれたmodは、扱うイベントの一覧つきで1行ずつ記録されます。
grep "hooks module" ./debug.log出力の形は、たとえば次のとおりです。
hooks module first-mod@inline loaded (worker, environment 2, tier user); events: session.start,tool.call,command.run,ui.renderevents: の並びに tool.call か turn.step があるmodが、書き換えの候補です。--plugin-dir で読み込んだmodは、名前の後ろに @inline が付きます。今回の拒否の理由はClaudeが読む側に返るものなので、modの読み込み失敗やhookのスキップのように警告の行として出るわけではありません。そうした別の症状の見分け方は、modが動かないときの切り分けで扱っています。modの tool.call の使い方は、modでtool.callを観測・書き換え・代理応答するで扱っています。
設定ファイルのhookを見る
modに該当がなければ、settings.json に書いた PreToolUse hookを疑います。hookのスクリプトが標準出力にJSONを返し、その中に updatedInput があれば、書き換えをしています。スクリプトの中身まで開いて確かめます。PreToolUse hookの出力の形は、PreToolUse hookでツール実行前に許可・拒否・改変するが詳しいです。
候補を1つずつ止める
見当がつかないときは、候補を1つずつ外して同じ操作を試します。modなら無効にし、設定のhookならその項目をいったんコメントアウトするかファイルから外します。拒否が消えた直前の変更が、犯人です。
書き換えhookが権限の評価に与える影響
PreToolUse hookが updatedInput を返すと、Claude Codeは権限ルールの評価を、Claudeが送った入力でなくhookが返した入力に対して行います。Bashコマンドの自動バックグラウンド化の可否も、同じくhookが返した入力で決まります。
つまり、hookが書き換えた先のコマンドが、allow や deny ルールに照らされます。permissionDecision: "allow" を併せて返せば許可の確認を省けますが、deny ルールと ask ルールは、hookの返答にかかわらず評価されます。rm -rf ~ のように重要なパスを対象にした rm と rmdir は、PreToolUse hookが allow を返しても承認されません。hookの許可は万能ではなく、書き換えで通る範囲にも限りがあります。
modの tool.check も同じ順序で、設定のhookやmodの tool.call が書き換えた後の入力で許可・確認・拒否を決めます。
auto modeで話が変わるのは、さらにその外側に分類器の審査があるためです。通常のモードなら、書き換え後の入力が権限ルールで評価されて済みます。auto modeでは、審査が見た入力と実行される入力が食い違うこと自体が拒否の理由になります。
PermissionRequest hookは候補に入るか
PermissionRequest hookも、allow を返すときに限って updatedInput で入力を変えられます。変えた入力は、deny ルールと ask ルールで改めて評価されます。ただし、拒否のメッセージが原因として挙げるのは、modの tool.call・turn.step と PreToolUse の3つだけです。PermissionRequest hookがこの拒否を起こすかどうかは、トラブルシューティングのページにもhooksのページにも書かれていません。3つを外しても拒否が消えず、PermissionRequest hookを入れているなら、それも1つずつ止めて試す対象になります。
症状の出方で見当をつける
次のような傾向があれば、書き換えhookを疑う根拠になります。
- 同じ操作が、auto modeでだけ拒否される(手動承認のモードでは通る)
- 拒否の文言に、分類器が選んだルール名(
[Data Exfiltration]のような表記)が付かない - 特定のツール、たとえばBashだけで起きる。hookやmodの
matcherがそのツールに絞られていることが多い
1点目と3点目は、書き換えhookの性質から導ける見当です。公式のページに、これで特定できるという記載はありません。最終的な確認は、先の「候補を1つずつ止める」手順で行います。
書き換えを避けたいmod作者へ
自作のmodやhookが原因だったなら、直し方は2通りです。
- auto modeで使う前提なら、
tool.callやPreToolUseの中で入力を変える処理を外し、観測や拒否({ deny: reason })に絞る - 入力の書き換えが機能の中心なら、auto modeで動かすと拒否されることを利用者に知らせておく
tool.call hookは next(e)、{ deny: reason }、{ result } を返せます。入力を変えずに止めたり、代わりの結果を返したりする使い方は、入力の書き換えとは別の経路です。ただし、auto modeでこれらが拒否されないと公式のページが述べているわけではないので、実際の挙動は手元で確かめてください。
同じ呼び出しが拒否され続けるとき
拒否のメッセージには、同じ呼び出しを記録されたとおりにもう一度出すようClaudeへ促す文面が含まれます。1度目の再発行で通れば、書き換えはその回だけのものでした。
2度目も拒否されたなら、そのhookは入力を毎回変えています。選べる道は3つです。
拒否が続くときの選択肢
modかhookを止める
書き換えが不要なら、modを無効にするか、
PreToolUsehookの該当項目を外します。auto modeを抜ける
Shift+Tabで切り替えるモードをauto以外にして、hookの書き換えを残したまま使います。自分で承認する
呼び出しをその場で確認し、手動で通します。
書き換えを残したいなら、審査と両立しないことは受け入れる必要があります。書き換えるhookを残してauto modeも使い続ける、という組み合わせはこのメッセージの前提で成り立ちません。
似た文言との見分け方
auto modeの拒否は、文言ごとに原因が別です。このメッセージを、近い話題と取り違えやすい文言と並べておきます。
| 画面の文言 | 何が起きているか | 読む記事 |
|---|---|---|
a hook changed this call's input after the model wrote it | 何が起きているか審査後にhookが入力を変えた | 読む記事本記事 |
tried to lift a deny rule in your settings | 何が起きているかmodの tool.check が、denyルールに止められる呼び出しを承認しようとした。呼び出しは拒否のまま | 読む記事modが動かないときの切り分け |
the deny rules in your settings could not be checked for this call, so it is refused | 何が起きているか組み込みのガードがdenyルールを確認できず、呼び出しを拒否した | 読む記事同上 |
| サーバーが安全性の判定を返さない | 何が起きているか審査結果が届かず、Claude Codeが実行を見送った | 読む記事auto modeの分類器が応答不能 |
拒否の理由に分類器が選んだルール名が付く場合は、hookではなく分類器そのものの判断です。拒否のあとの再試行をhookで制御したいときは、PermissionDenied hookが別の入口になります。
まとめ
メッセージの中身は「審査のあとに入力が変わった」という一点です。modの tool.call・turn.step と、設定の PreToolUse の3つを順に外せば、書き換え元は特定できます。再発行しても拒否が続くなら、そのhookを止めるか、auto modeを使わないか、自分で承認するかを選ぶことになります。