[BUG] PreToolUse hook updatedInput replaces entire tool_input instead of merging, silently drops unspecified fields
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
- Create a
PreToolUsehook that modifies only thepromptfield of aTasktool 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))
- Register the hook in
.claude/settings.json:
{
"hooks": {
"PreToolUse": [{
"matcher": "Task",
"hooks": [{"type": "command", "command": "python3 .claude/hooks/inject_context.py"}]
}]
}
}
- Have Claude use the Task tool with
subagent_type: "general-purpose"
- 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
updatedInputon a multi-parameter tool (Task, Bash, etc.) - Silent failure — no warning that fields were dropped
- Particularly severe for Task tool since
subagent_typeis 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)
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗