[BUG] PreToolUse/Edit hook not triggered when old_string doesn't match file content
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?
Summary
The PreToolUse hook with Edit matcher is not triggered when the old_string parameter doesn't exist in the target file. Claude Code appears to validate old_string against file content before invoking the hook, preventing the hook from correcting mismatched parameters (such as indentation differences).
Description
When configuring a PreToolUse hook for the Edit tool, the hook should be invoked before the tool executes, giving it the opportunity to inspect and modify the tool inputs. This is essential for third-party hooks like claude-tab-fix which correct indentation mismatches between old_string (generated by the model) and the actual file content.
However, our testing reveals that:
- When
old_stringexactly matches file content → hook is triggered correctly - When
old_stringdoesn't match file content → hook is NOT triggered at all
Instead, Claude Code directly returns an error: "String to replace not found in file", bypassing the PreToolUse hook entirely.
What Should Happen?
The PreToolUse hook should be invoked before any validation of old_string. The hook should have the opportunity to:
- Detect the mismatch (e.g., file uses tabs,
old_stringuses spaces) - Correct the parameters
- Return modified
old_stringandnew_stringfor retry (exit code 2 with corrected strings on stderr)
This is consistent with the documented hook output format where exit code 2 surfaces stderr as feedback, allowing the model to retry with corrected inputs.
Error Messages/Logs
Steps to Reproduce
- Create a tab-indented file (e.g.,
main.gousing tabs for indentation) - Install claude-tab-fix and configure a PreToolUse/Edit hook in
.claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit",
"hooks": [{ "type": "command", "command": "claude-tab-fix" }]
}
]
}
}
- Ask Claude to edit the file in a way that the model generates
old_stringwith space indentation (e.g., 8 spaces instead of tabs) - Observe: The error "String to replace not found" is returned immediately
- Check hook logs: No entry for the Edit tool call at all
Claude Model
None
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
2.1.97
Platform
Other
Operating System
Other Linux
Terminal/Shell
VS Code integrated terminal
Additional Information
_No response_
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗