[MODEL] Frequently uses Bash tools (sed/grep/etc) when use-case is well aligned to other builtin tools (Read/Grep/etc)
Preflight Checklist
- [x] I have searched existing issues for similar behavior reports
- [x] This report does NOT contain sensitive information (API keys, passwords, etc.)
Type of Behavior Issue
Other unexpected behavior
What You Asked Claude to Do
See attached (redacted, and line-wrapped for readability) for a full exchange analyzing occurrences based on claude/agent logs. The analysis ran afoul of some of the same issues being analyzed.
What Claude Actually Did
I frequently see Bash tool permission requests invocations just like the following:
# Output lines 147-162 from a given file.
sed -n 147,162p /path/to/some/file
# Search for a pattern in a directory.
grep -r "^some pattern$" /path/to/some/dir
# Write a file from content expressed via heredoc.
cat > file.txt <<EOF ... EOF
Often these are just solitary commands (not as part of a pipeline). Sometimes, e.g. grep, might be piped to head. Sometimes these &&'d commands like:
cat > file.py <<EOF ... EOF && chmod +x file.py && ./file.py
Expected Behavior
All of the above examples are undesirable.
These cases should broadly use the Read, Grep (builtin, not Bash(grep)), and Write/Edit.
The &&'d example above ideally should use Write followed by Bash tool call(s).
Files Affected
N/A
Permission Mode
Accept Edits was ON (auto-accepting changes)
Can You Reproduce This?
Sometimes (intermittent)
Steps to Reproduce
Sufficiently long code-investigation/debugging sessions will usually involve seeing many permission prompts matching these undesirable patterns.
Claude Model
Sonnet
Relevant Conversation
N/A
Impact
Medium - Extra work to undo changes
Claude Code Version
v2.1.2
Platform
AWS Bedrock
Additional Context
I'll let Claude's own analysis conclusions explain why this matters (everything that follows is from Claude own reflection)...
Why This Matters
Built-in tools (Read, Grep, Write, Edit) are specifically designed to work efficiently with Claude Code's permission system. Using bash commands bypasses these optimizations, resulting in slower task completion and more human time spent on reviews. The permission system can cache approvals for built-in tool operations but cannot cache unique bash command content like heredocs, creating significant efficiency penalties in human-in-the-loop workflows.
Key Findings
1. sed -n for line ranges (45 cases, ~40 problematic)
When users mention line ranges (e.g., "lines 253-343" or "around line 150"), Claude agents often reach for sed -n because it directly translates the user's language. The Read tool requires offset/limit arithmetic (offset=252, limit=91), creating cognitive overhead. The root issue is parameter mismatch: users think in line numbers, but the Read tool speaks in offsets and limits. Solution: Add start_line/end_line parameters to the Read tool so agents can write Read(start_line=253, end_line=343) directly, eliminating the mental translation step and making the tool as intuitive as sed.
2. grep for searching (45 cases, ~30 problematic)
Claude agents use bash grep commands for operations like counting matches (grep -c), showing context lines (grep -A/-B/-C), or recursive searching (grep -r) because these features aren't obviously discoverable in the Grep tool. While the Grep tool supports all these capabilities (output_mode="count", -A=N, -B=N, -C=N parameters), they're buried in documentation. Agents fall back to familiar bash idioms because grep -c feels more direct than Grep(output_mode="count"). Solution: Enhanced system prompt with concrete examples showing how common grep patterns translate to Grep tool calls, and improved tool descriptions that prominently highlight these features.
3. cat heredoc for file creation (127 cases, ~127 problematic)
This is the most critical pattern because it represents a fundamental permission efficiency problem. Claude agents believe that writing file content inline with a heredoc bash command is more efficient, but this is a false efficiency heuristic. The permission system cannot cache heredoc approvals because each heredoc contains unique content, requiring full human review every time. Write tool operations complete reviews more efficiently and benefit from permission caching. When files need iteration (common for debugging scripts or refining content), heredocs require full reviews for every revision, while Edit tool presents diffs that dramatically reduce human review time for subsequent revisions. This applies to ALL file types: scripts, documentation, test fixtures, configs, temporary files. Solution: System prompt guidance emphasizing that Write/Edit tools are strictly more efficient for ALL file creation due to permission system architecture, particularly when considering human-in-the-loop review time.
Recommended Actions
Read tool enhancement:
- Add
start_lineandend_lineparameters - Keep existing
offset/limitfor compatibility - Example:
Read(file_path="/path/to/file", start_line=253, end_line=343)
System prompt updates:
- Add user language translation guide: "lines X-Y" → Read tool with start_line/end_line
- Add permission efficiency guidance: Write/Edit for ALL file creation (never cat heredoc)
- Explain why: permission caching and diff-based reviews make Write/Edit more efficient for human-in-the-loop workflows
- Add Grep tool feature examples: counting, context lines, recursive search
Tool description improvements:
- Grep tool: Prominently show output_mode="count" and context parameters
- Read tool: Explain start_line/end_line as intuitive alternative to offset/limit
25 Comments
I think it is time to BAN claude from using those tools. Making it understand the tools tailored for LLM agents and make those tools full-featured is the correct path. "bash for everything" is just wrong.
I run into the same issue all the time. It'd be great to fix this.
This is becoming a major problem.
Sometimes if I tell it to remember to not use sed (in CLAUDE.md or whatever) then it tries to be more creative and writes its own node scripts to search and replace tabs vs spaces for example 😂
Hilarious, but very dumb and inefficient and not leading to anything.
Engineer reported this after having installed the
superpowersplugin from marketplace, not sure if they are related at all, seeing this as a more frequent issues this past few days.It also enters a mode to ask for permission to run the command because is potentially dangerous. Like a grep with absolute path forward errors to /dev/null pipe head or pipe tail. So there needs to be a review, don't understand why it would default to a "escape sandbox" behaviour instead of following the guardrails.
Just ran into this issue as well, fwiw.
Same for me. I often need to tell Claude to use its own Read util.
I use AWS Bedrock, and no
superpowersplugin, btw.In case it's useful to others facing this issue, I've asked Claude to add a hook to automatically deny them. It added this hook to my project:
I'm not sure the
lsresponse totally make sense, since it wouldn't able to call it anymore even if I explicitly ask it to. But for me it does the job for now.Thanks @fgascon , I got claude to extend that script a little, seems to be working well at preventing find, grep etc...
@extemporalgenome For the
greppiped toheadpattern mentioned in the analysis, I've been using:This flattens output to a single line with
[N]markers, so subsequent| head -n 20calls don't actually truncate—they just return the entire line since there's only one.Doesn't address the preference for bash over built-in tools, but it prevents the "head limit was too small" repetition loops when agents do use bash+pipe patterns.
The hook approach from @fgascon and @dcerisier is the right user-side workaround. One gap worth noting: simple
split(" ")[0]parsing won't catch commands embedded in pipes or chains. For example,echo foo | grep patternpasses through because the first token isecho, notgrep.Here's a version that checks all segments of piped/chained commands:
Hook config in
settings.json:This catches
grepeven after a pipe (cmd | grep) or in chained commands (cmd && grep). It also handles path-prefixed commands (/usr/bin/grep) and env-var prefixes (LANG=C grep).That said, this is a band-aid — the root cause is in model behavior. The built-in tools already cover nearly every use case (Read supports
offset/limit, Grep supports-A/-B/-Ccontext andoutput_mode="count", Glob handlesfindpatterns), but the model doesn't consistently reach for them."That said, this is a band-aid — the root cause is in model behavior."
Just came to say +1 on this. As a heavy user of Claude models in harnesses other than Claude Code, I noticed this a long time ago. Built a slash command whenever I saw this occur. All it really does is send the following on /tool-use:
Always prioritize using the tools you have over equivalent terminal commands.Always thought perhaps poorly optimized harnesses were to blame. Turns out Claude itself needs to be better about this 🙂.
Adding concrete evidence to this — I hit the same behavior on Windows 11 with claude-opus-4-6 (1M context).
What makes this particularly notable is that Claude Code's own system prompt explicitly prohibits it:
Yet in my session, Claude used
cat,grep, andfindvia Bash for file reading and searching despite having Read, Grep, and Glob available. This isn't just a preference issue — the model is actively violating its own instructions.I filed #39979 about this but closing it in favor of this issue since it covers the same core problem. Hopefully the system prompt angle adds useful context for the fix.
Adding evidence from #39979 (consolidating here per @mimuelas's suggestion).
The system-prompt-violation angle: Claude Code's own system prompt explicitly says _"Do NOT use the Bash to run commands when a relevant dedicated tool is provided"_ and lists specific mappings (Read instead of cat, Grep instead of grep, Glob instead of find, Edit instead of sed). Despite this, the model violates this instruction at a roughly 40% rate in our measurement across 200+ sessions on a 2M LOC C++ codebase (Windows 11, Opus 4.6).
When it's worst:
grep -r pattern dir | head -20has no single dedicated-tool equivalent, so the model generalizes and uses Bash for simple cases too.What doesn't work: CLAUDE.md rules. We have explicit instructions reinforcing the system prompt prohibition. Compliance is ~100% for the first ~30 minutes, then degrades. The model will sometimes _quote_ the rule in its thinking block and then violate it in the same response.
What does work (partially): The PreToolUse hook approach shared by @yurukusa and others in this thread. We haven't deployed it yet but the pattern is sound — deny Bash calls that match standalone
cat,grep,find,head,tailand force the model to retry with the dedicated tool.The hooks are a good workaround but the root cause is model behavior — this needs to be addressed in training or prompting, not bolted on by users.
FWIW - Using Claude Sonnet and Opus in OpenCode via API access has a far better tool adherence than what I'm seeing in Claude Code. To me, that indicates it's the Claude Code harness and perhaps a bug in their context management strategy.
The Claude Code editor in VSCode sometimes shows three (3) tabs around code when doing an Edit operation, but in the actual file there is only two (2) tabs. And 4 tabs when there is 3 etc. Those operations always fail, and then it turns to
sed.even with the pretooluse hook, I see Claude doubling down and doing what it wants.
I have found when denying the affect bash/sed/awk commands, Claude gets completely sidetracked from the original task.
Instead of using it's tools, it starts trying to understand why it doesn't have permission to run various bash commands. It starts looking through .claude/projects to discover ways to circumvent the bash permisisons denied.
These are a selection of calls it tries to make on it's road to discovery, it's persistent.
But something is going wrong. Why will it spend tons of tokens trying to figure this out instead of just obeying the deny and using the tools it has for searching, reading, writing etc.
Even starts writing for loops
... and python code to assist in it's discovery
@raldred works fine for me, but my deny inserts a message of why that says, the user requests that you use this other tool instead.
@Caleb-KS you're using a pre tool hook to inject the deny message?
@raldred that's right. I also have it tell claude it can rerun the exact same tool call and it will go through the second time. I've found if you couch it as, "the user prefers" rather than "dont do that", claude will respect that more instead of trying to defeat your mechanism.
I think there's something else going on with mine then because it refuses to believe it has the Grep tool.
LSP — workspaceSymbol, findReferences, goToDefinition for symbol-level lookups in Ruby code
Read — for known paths
Bash — last resort for free-text search
Grep/Glob appear to not be provisioned for this session. I'll lean on LSP and Read as much as possible, and only use Bash grep/rg when I genuinely need free-text search across unknown files
Just come across this
https://code.claude.com/docs/en/changelog#2-1-117
That might explain it.
ah, ok. That's good to know. I'll have to update my guidance hook.
I'm also seeing this with Claude Code running Opus 4.7 on Windows 11. Claude will often try running various bash tools instead of the built-in tools, causing unnecessary permission prompts. Claude suggested adding the following to CLAUDE.md.
\- Use the dedicated tools, not Bash equivalents: \\Glob** for finding files
(not
find), \\Grep** for searching contents (notgrep), \\Read** forinspecting a file (not \
cat). The dedicated tools integrate with thepermission UI and return clickable file links.
However, if this suggestion is already part of the system prompt and it's being ignored, I don't know how helpful it will be.
That seems to indeed be the explanation. On my Mac with Claude 2.1.154: