claude-code - ✅(Solved) Fix PreToolUse agent hooks fail with 'Messages are required' despite schema requiring only 'prompt' [1 pull requests, 1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
anthropics/claude-code#49898Fetched 2026-04-18 05:56:50
View on GitHub
Comments
1
Participants
2
Timeline
8
Reactions
0
Timeline (top)
labeled ×4cross-referenced ×3commented ×1

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.

Error Message

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. 3. Observe the error surfaced by the harness: PreToolUse:Bash hook error

Root Cause

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.

Fix Action

Fixed

PR fix notes

PR #316: #276 Redact cookies, bearer tokens, and emails from AI diagnostic egress

Description (problem / solution / changelog)

Summary

Closes #276. Contributes to #273.

Adds redactSensitive() in playwright/typescript/utils/diagnosis.util.ts and applies it to the DOM snapshot, each console log line, and assertion error messages before they cross the AI_DIAGNOSIS=true boundary to Anthropic / Gemini. Four patterns:

CategoryPattern (case-insensitive)Replacement
Cookie header valueCookie: <name>=<value> (value until ;, ", <, >, newline)Cookie: <name>=[REDACTED]
Bearer token (with header)Authorization: Bearer <token>Authorization: Bearer [REDACTED]
Bearer token (standalone)bearer <token> where token is 12+ chars of A-Za-z0-9._-bearer [REDACTED]
Email local-part<local>@<domain><first-char>***@<domain>

Redaction runs before the 30 000-char truncation so a cut-off token cannot leak its prefix through a naïve slice. Terminator character classes deliberately exclude ", <, >, ', ` so a Bearer <token></pre> shape cannot swallow the closing XHTML tag — verified by a structural-integrity test that counts <, >, &, " before/after redaction and optionally runs the result through xmllint --noout when libxml2 is available.

On-disk Playwright dom.xhtml attachments are not redacted — redaction happens only on the in-memory copy sent to the AI provider, preserving the unredacted artifact for local post-mortem.

What is not redacted (documented in README)

  • Full XHTML DOM structure (tags, attribute names, classes) — fingerprints the app.
  • Test metadata: title, project name, status, expected status.
  • Error messages verbatim apart from the four patterns above.
  • Base URL, paths, query strings visible in DOM / console.
  • Session IDs, CSRF tokens, or any secret not matching the four patterns (e.g. JWTs in data-token, CSRF tokens in hidden <input>s).

Unit tests

New playwright/typescript/utils/__tests__/diagnosis.util.test.ts (Node built-in test runner; no new npm deps). Covers cookie, bearer (header + standalone), email, mixed DOM, idempotence, no-match passthrough, 30 k char-budget preservation, XHTML structural integrity (</>/&/" counts unchanged + optional xmllint round-trip), and multi-line per-log redaction. Runs via npm run test:unit, and a new Unit tests step on playwright-typescript-lint.yml exercises it on every PR.

Live end-to-end verification

Exercised attachAiDiagnosis() against a local HTTP intercept server (ANTHROPIC_BASE_URL pointed at 127.0.0.1:8787, fake API key) with a synthetic testInfo whose DOM, console logs, and error messages contained all seven of these known secrets:

  • Cookie: session=s3cret_abc_123xyz (in <script>)
  • Cookie: session=log-level-secret-xyz987 (in console log)
  • Authorization: Bearer tok_supersecret_ABCDEFGHIJ1234 (in <pre>)
  • Authorization: Bearer err_tok_9988776655 (in assertion error message)
  • [email protected] (in <meta> attribute + console log)
  • [email protected] (in rendered <a mailto:>)
  • [email protected] (in assertion error message)

Captured the outbound JSON body and asserted: 0 of 7 raw secret substrings appear in the payload; all 4 expected redaction markers ([REDACTED], a***@example.com, a***@example.org, b***@example.com) are present; payload stays under the 30 k char budget.

Test plan

  • npm run format:check clean on all modified files
  • npm run tsc clean with new noEmit: true + allowImportingTsExtensions: true in tsconfig.json
  • npm run test:unit — 11/11 pass locally
  • Unit test exercising realistic XHTML fragment confirms redaction does not introduce <, >, &, " characters (XHTML parsing preserved)
  • Call-site applies redactSensitive to domContent before truncation so cut-off tokens cannot leak their prefix
  • Saved dom.xhtml attachment remains unredacted (redaction scoped to the AI-provider send path only)
  • Live end-to-end: ran attachAiDiagnosis() against a local intercept server with 7 seeded secrets across DOM / logs / errors; captured payload contained 0 leaked secrets and all 4 expected redaction markers
  • CI playwright-typescript-lint workflow passes with the new Unit tests step (CI to verify)

Note (not part of this ticket)

The drive-by request in the #276 thread to swap the .claude/settings.json agent-hook prompt field to messages was declined on this branch — the Claude Code schema explicitly requires prompt: string for agent hooks and forbids messages (additionalProperties: false), so applying the diff would make settings.json refuse to load. The observed Failed to run: Messages are required for agent hooks. This is a bug. is an upstream Claude Code bug (the error message itself says so); filed as https://github.com/anthropics/claude-code/issues/49898. The fix-issue skill's Step 4b still runs /deep-review manually on every commit, so the broken PreToolUse layer is a belt-and-suspenders gap, not a review bypass.

Changed files

  • .github/workflows/playwright-typescript-lint.yml (modified, +2/-0)
  • README.md (modified, +84/-57)
  • playwright/typescript/package.json (modified, +1/-0)
  • playwright/typescript/tsconfig.json (modified, +2/-1)
  • playwright/typescript/utils/diagnosis.util.test.ts (added, +117/-0)
  • playwright/typescript/utils/diagnosis.util.ts (modified, +36/-9)

Code Example

{
     "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
             }
           ]
         }
       ]
     }
   }

---

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

---

{
  "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" }
  }
}
RAW_BUFFERClick to expand / collapse

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:

    {
      "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
              }
            ]
          }
        ]
      }
    }
  2. Inside a Claude Code session against that repo, run git commit -m "test" via the Bash tool.

  3. Observe the error surfaced by the harness:

    PreToolUse:Bash hook error
    Failed to run: Messages are required for agent hooks. This is a bug.
  4. 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.

extent analysis

TL;DR

Update the runtime validator to accept prompt and internally construct the required messages array, or modify the schema to allow both prompt and messages for backwards compatibility.

Guidance

  • Review the schema definition for type: "agent" hooks to ensure it aligns with the runtime expectations, specifically regarding the prompt and messages fields.
  • Consider updating the runtime to handle the conversion from prompt to messages internally, to maintain backwards compatibility with existing configurations.
  • If modifying the schema, ensure to update the documentation to reflect the changes and provide clear guidance on using prompt versus messages.
  • Test the updated configuration with various PreToolUse hooks to verify the fix and ensure no regressions.

Example

No code snippet is provided as the issue is more related to configuration and schema definition rather than code implementation.

Notes

The suggested fix requires careful consideration of backwards compatibility and the potential impact on existing configurations. It's essential to thoroughly test the updated solution to ensure it works as expected and does not introduce new issues.

Recommendation

Apply a workaround by updating the runtime validator to accept prompt and construct the messages array internally, as this approach maintains backwards compatibility and minimizes the risk of disrupting existing configurations.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING