結論

フックが返す不正な updatedInput は、旧版の 2.1.299 でも実行前に止まっていました。 2.1.300 以降で変わったのは、エラー文です。

  • 止まるか通るかは、試した 4 通りすべてで両版とも同じ
  • PermissionRequest フックのエラー文は、新版で「どのフックが原因か」を名指しするようになった
  • ツールにない項目を足しても、両版とも結果は変わらなかった

どれも各ケース 1 回ずつ動かした結果です。

きっかけ

2.1.300 のリリースノートに、次の 1 行があります。

updatedInput from PreToolUse and PermissionRequest hooks is now validated like Claude's own input

フックの updatedInput で、ツールへの入力を書き換えている人には気になる変更です。 そこで、変更前の 2.1.299 と、執筆時点で最新の 2.1.306 で、同じフックを動かして比べました。

試した環境

  • Windows 11 / Node.js 22.16
  • Claude Code 2.1.306 と 2.1.299(旧版は別フォルダに npm i で入れた)
  • claude -p で実行、モデルは Claude Haiku 5.5
  • フックは --settings で読み込ませた

試した 4 通りと結果

1. コマンドを書き換え、ツールにない項目を足す(PreToolUse・Bash)

  • 2.1.299: 書き換え後のコマンドが動いた
  • 2.1.306: 同じ

2. ツールにない項目だけを足す(PreToolUse・Read)

  • 2.1.299: 普通にファイルを読めた
  • 2.1.306: 同じ

3. コマンドを数値にする(PreToolUse・Bash)

  • 2.1.299: 実行前に止まった
  • 2.1.306: 実行前に止まった。エラー文の最後に 1 文増えた

4. 許可を出しつつ、コマンドを数値にする(PermissionRequest・Bash)

  • 2.1.299: 実行前に止まった。エラー文は原因の候補を 3 つ並べるだけ
  • 2.1.306: 実行前に止まった。エラー文で PermissionRequest フックを名指し

エラー文はどう変わったか

4 の PermissionRequest の場合です。1 行目を比べると違いが分かります。

2.1.299

The permission handler returned updatedInput for Bash
that failed schema validation

続けて「canUseTool のコールバック、PermissionRequest フック、permission-prompt ツールのどれかの設定の問題」と書かれます。どれなのかは分かりません。

2.1.306

PermissionRequest hook for Bash returned updatedInput
that failed schema validation

最後には、次の 1 文が付きます。

The tool input from the model was valid;
the hook's rewrite of it was not.

「直すのはフックの側」と、すぐに分かります。

3 の PreToolUse の場合は、旧版でも 1 行目で PreToolUse hook for Bash と名指ししていました。 新版で増えたのは、この最後の 1 文だけです。

(読みやすさのため、エラー文は途中で改行しています)

ほかに気づいたこと

Claude は同じ呼び出しをもう一度試した。 4 の場合、両版とも Claude が同じ mkdir をもう一度実行し、2 回とも止まりました。 フックは毎回同じように書き換えるので、2 回目も必ず失敗します。

PermissionRequest フックは、許可の要らないコマンドでは呼ばれない。 echo では呼ばれず、mkdir で呼ばれました。

「未知の項目を落とす」は、外からは確かめられなかった。 リリースノートには unknown keys are dropped(ツールにない項目は落とす)とあります。 ただ、1 と 2 は両版とも結果が同じで、落とされたかどうかは分かりませんでした。

フックを書く人へ(試していない提案)

ここからは提案です。提案そのものは試していません。

  • 書き換えた入力は、フックの中でも確かめる。 不正なら書き換えずに、permissionDecision: "deny" と理由(permissionDecisionReason)を返す。Claude の同じ呼び出しの繰り返しを減らせるかもしれない
  • PermissionRequest で拒否するときは書き方が違う。 decision: { behavior: "deny", message: "..." } で返す
  • すべての呼び出しを見たいなら PreToolUse を使う。 PermissionRequest は、許可の要らない操作では呼ばれない

自分で試すには

作業用のフォルダに次の 2 つを置きます。

hook.js(許可は出すが、コマンドを数値に書き換える)

let s = "";
process.stdin.on("data", (c) => (s += c)).on("end", () => {
  const ev = JSON.parse(s);
  process.stdout.write(JSON.stringify({
    hookSpecificOutput: {
      hookEventName: "PermissionRequest",
      decision: {
        behavior: "allow",
        updatedInput: { ...ev.tool_input, command: 12345 },
      },
    },
  }));
});

settings.json

{
  "hooks": {
    "PermissionRequest": [
      {
        "matcher": "Bash",
        "hooks": [{ "type": "command", "command": "node hook.js" }]
      }
    ]
  }
}

次のコマンドで動かします。

claude -p "Run this Bash command: mkdir test-dir" --settings settings.json

旧版と比べるときは、別のフォルダで npm i @anthropic-ai/claude-code@2.1.299 を実行し、node_modules/.bin/claude を使います。


この記事は Hiroba 編集部(Koji)が、手元で実際に動かした結果をもとに書きました。 リリースノートの原文: Claude Code v2.1.300