PreToolUse agent hooks fail with 'Messages are required' despite schema requiring only 'prompt'

Resolved 💬 3 comments Opened Apr 17, 2026 by hubertgajewski Closed Apr 21, 2026

Summary

PreToolUse hooks of type: "agent" have a schema/runtime contract mismatch. The settings.json JSON schema requires prompt: string and explicitly forbids messages (via additionalProperties: false), but at runtime the harness rejects the hook with Failed to run: Messages are required for agent hooks. This is a bug. — the error message itself flags the defect.

Net effect: every PreToolUse hook of type: "agent" is a silent no-op on its matching tool call. A git commit review guardrail configured this way never runs.

Steps to reproduce

  1. Add to project .claude/settings.json:

``json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "agent",
"if": "Bash(git commit*)",
"prompt": "Review the staged changes before committing.",
"model": "claude-sonnet-4-6",
"timeout": 600
}
]
}
]
}
}
``

  1. Inside a Claude Code session against that repo, run git commit -m "test" via the Bash tool.
  2. Observe the error surfaced by the harness:

``
PreToolUse:Bash hook error
Failed to run: Messages are required for agent hooks. This is a bug.
``

  1. Note that the git commit itself succeeds — the agent hook is bypassed entirely.

Expected vs actual

Expected: the harness wraps the prompt string into whatever internal messages shape the agent runner requires, executes the review agent against the repo, and returns its allow/block decision to the pre-commit gate (matching the behaviour documented for agent-type hooks).

Actual: the runtime validator demands a messages field that the schema does not allow. The hook aborts, the commit proceeds unreviewed, and the only user-visible signal is a one-line harness warning.

Schema vs runtime (copied from the live schema)

{
  "type": "agent",
  "required": ["type", "prompt"],
  "additionalProperties": false,
  "properties": {
    "type": { "const": "agent" },
    "prompt": { "type": "string", "description": "Prompt describing what to verify..." },
    "if": { "type": "string" },
    "timeout": { "type": "number" },
    "model": { "type": "string" },
    "statusMessage": { "type": "string" },
    "once": { "type": "boolean" }
  }
}

Any attempt to work around the runtime by adding messages: [{role, content}] is rejected at settings-load time by the schema above, so users have no local-config escape hatch.

Impact

  • PreToolUse agent hooks targeting Bash(git commit*) (and similar) cannot enforce pre-commit review in any repo.
  • The failure is silent in the sense that it does not block the triggering tool call; users only notice when they re-read the scroll or audit the review trail.
  • Any security-sensitive guardrail (/security-review, /deep-review, policy gates) wired up via type: "agent" hooks is inert.

Environment

  • Claude Code CLI (Opus 4.7 1M context channel, but the schema/runtime code path is model-agnostic)
  • macOS 25.4.0, bash shell
  • Repro independent of MCP server configuration.

Suggested fix

Make the runtime accept prompt and internally construct the messages array it needs, so the schema and the validator agree. Alternatively: widen the schema to accept messages and keep prompt as a backwards-compatible convenience — and update the docs to reflect the chosen shape.

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗