[BUG] PreToolUse hook updatedInput replaces entire tool_input instead of merging, silently drops unspecified fields

Resolved 💬 3 comments Opened Feb 20, 2026 by alkari Closed Feb 23, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

When a PreToolUse hook returns updatedInput with modified fields, it replaces the entire tool_input object rather than merging with it. Any fields not explicitly included in updatedInput are silently set to undefined.

This is particularly destructive for the Task tool, where omitting subagent_type from updatedInput causes the error:

Agent type 'undefined' not found. Available agents: Bash, general-purpose, statusline-setup, Explore, Plan, claude-code-guide

Steps to Reproduce

  1. Create a PreToolUse hook that modifies only the prompt field of a Task tool call:
# .claude/hooks/inject_context.py
import json, sys

input_data = json.load(sys.stdin)
tool_input = input_data.get('tool_input', {})
prompt = tool_input.get('prompt', '')

output = {
    "hookSpecificOutput": {
        "hookEventName": "PreToolUse",
        "permissionDecision": "allow",
        "updatedInput": {
            "prompt": "Additional context.\n\n" + prompt
        }
    }
}
print(json.dumps(output))
  1. Register the hook in .claude/settings.json:
{
    "hooks": {
        "PreToolUse": [{
            "matcher": "Task",
            "hooks": [{"type": "command", "command": "python3 .claude/hooks/inject_context.py"}]
        }]
    }
}
  1. Have Claude use the Task tool with subagent_type: "general-purpose"
  1. Result: Agent type 'undefined' not found

What Should Happen?

updatedInput should merge with the original tool_input, only overriding specified fields. The documentation example implies this behavior:

"updatedInput": {
    "field_to_modify": "new value"
}

The wording "field_to_modify" suggests partial update semantics, not full replacement.

At minimum, if full replacement is intentional, the documentation should clearly state: "updatedInput replaces the entire tool input. You must include ALL original fields you want to preserve."

Workaround

Pass through all original fields and override only the ones you need:

updated_input = dict(tool_input)  # copy ALL original fields
updated_input["prompt"] = modified_prompt  # override just the one you need

Environment

  • Claude Code version: 2.1.49 (also reproduced on 2.1.34 through 2.1.47)
  • Platform: Linux (VS Code Remote-SSH, arm64)
  • OS: Ubuntu 24.04

Impact

  • Affects any hook that uses updatedInput on a multi-parameter tool (Task, Bash, etc.)
  • Silent failure — no warning that fields were dropped
  • Particularly severe for Task tool since subagent_type is required but invisible to the hook author
  • Likely affecting other users who write compliance/context-injection hooks (common enterprise pattern)

Related Issues

  • #19124 (inconsistent updatedInput schema across hook events)
  • #15897 (updatedInput ignored with multiple hooks)
  • #4368 (original feature request for updatedInput)

View original on GitHub ↗

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