[BUG] PreToolUse updatedInput silently ignored for Edit tool — works for Read and Bash
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?
PreToolUse hooks that return permissionDecision: "allow" with updatedInput have the updatedInput completely ignored for the Edit tool. The original tool input is executed instead of the modified input. The Edit tool then fails because the original old_string doesn't match the file.
updatedInput works correctly for:
- Read —
updatedInputwith modifiedoffset/limitis applied (confirmed in existing setup) - Bash —
updatedInputwith modifiedcommandis applied (confirmed in existing setup)
The hooks documentation lists Edit as a supported tool for updatedInput.
What Should Happen?
When a PreToolUse hook returns permissionDecision: "allow" with updatedInput containing {file_path, old_string, new_string, replace_all}, the Edit tool should execute with the updated old_string.
Reproduction
Single hook setup (settings.json — only ONE PreToolUse entry, no plugin hooks):
"PreToolUse": [
{
"hooks": [
{
"type": "command",
"command": "node my-hook.js",
"timeout": 10
}
]
}
]
Hook script outputs valid JSON with corrected old_string:
process.stdout.write(JSON.stringify({
hookSpecificOutput: {
hookEventName: 'PreToolUse',
permissionDecision: 'allow',
updatedInput: {
file_path: '/path/to/file.js',
old_string: 'corrected old string that exists in file',
new_string: 'new content',
replace_all: false
}
}
}));
Verified manually: piping the same stdin to the hook script produces the correct JSON output with all 4 Edit parameters. The corrected old_string IS present in the file on disk.
Result: Edit tool receives the original uncorrected old_string and fails with "String to replace not found in file."
Tested Scenarios
| Variation | Result |
|-----------|--------|
| permissionDecision: "allow" + updatedInput | ❌ Ignored for Edit |
| permissionDecision: "ask" + updatedInput | ❌ Ignored for Edit |
| updatedInput with additionalContext | ❌ Ignored |
| updatedInput without additionalContext | ❌ Ignored |
| Single hook in settings.json (no plugin hooks) | ❌ Ignored |
| Single hook in plugin hooks.json (no settings hooks) | ❌ Ignored |
| Hook routed via settings.json spawnSync to Python script | ❌ Ignored |
| permissionDecision: "deny" with permissionDecisionReason | ✅ Works (Edit blocked, reason shown) |
| updatedInput for Read tool (offset/limit) | ✅ Works (input modified) |
| updatedInput for Bash tool (command) | ✅ Works (command rewritten) |
All 4 Edit parameters are provided in updatedInput: file_path, old_string, new_string, replace_all.
Use Case
I have a PreToolUse hook that detects when a linter/formatter modified a file after Claude's last read (e.g., indentation changed from 4-space to 2-space). The hook corrects Claude's stale old_string to match the current file content and returns it via updatedInput.
Current workaround: use permissionDecision: "deny" with the corrected old_string in the reason, forcing Claude to retry manually. This adds a round-trip per correction.
Related
- #15897 —
updatedInputignored when multiple PreToolUse hooks execute (different root cause — our bug reproduces with a single hook)
Environment
- Claude Code version: Latest (VS Code extension)
- Platform: Windows 11
- Hook source: Both settings.json and plugin hooks.json tested independently
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗