[BUG] Claude Code Search / Grep Tool Critical Bug Report
Claude Code Grep Tool Critical Bug Report
Executive Summary
Only the first two paragraphs are written by a human. I have been prompting claude to capture some search failures and together we worked on some ideas for search approval. The doc is long, largely because I want to acknowledge that search is harder than it seems and I want to offer solution ideas not just be a complainer.
I have to remind Claude C. to use grep -r ~20-50 times a day, even though its in several claude.md files. As Claude developers, I trust you better know exact implementation details (grep, rg, find, fd, globbing patterns, etc. So, anything that sounds prescriptive in terms of implementation is only meant as inspiration. Below you'll see that I recommend a search-strategy/mode hot key system, just like edit/plan mode.
---------------------------------------- Almost All Claude Written Below ------------------------------------------ In my experience, the Grep tool in Claude Code has an estimated ~50% failure rate with significant delays and failures in happening frequently. Here are 7 documented test cases where it failed to find content that definitively exists in the codebase. This is a significant issue that causes:
- False negatives: Reports "No matches found" when matches exist
- Wasted developer time: Users must correct Claude's failed searches
- Incorrect conclusions: Claude assumes code/files don't exist and attempts to create duplicates
- Migration failures: Missing 161 React migration instances that need fixing
Critical Statistics
- 7 documented failures over 3 weeks (July 12 - August 6, 2025)
- ~50% estimated failure rate (these are just the documented cases)
- Significant delays even when it eventually works
- 161 matches missed in the most recent failure (Issue #7)
-
Near 100% success rate with bash
grep -ras alternative
Immediate Recommendation
Disable or fix the Grep tool immediately. Every search should use bash grep -r until the Grep tool is reliable.
Bug Pattern
- Grep tool returns "No matches found" or "No files found"
- Bash
grep -rimmediately finds the content - Content definitively exists at the searched location
- Failures occur with:
- Simple string searches
- Glob patterns (
**/*.ts,**/*.tsx) - OR patterns (
pattern1|pattern2) - Case-sensitive searches (doesn't default to
-i) - Recursive directory searches
Detailed Search Problems Log
Issue 1: Failed to find streamFromCache method
Date: 2025-07-12
What happened
User asked me to show the streamFromCache method. I attempted multiple searches that all failed:
-
Grepfor "streamFromCache" in/home/dwayne/pd/dq/src/main/java/com/pd/dq/synth/SynthMethodParams.java- No matches -
Grepfor "streamFromCache" in/home/dwayne/pd/dq/src/main/java/com/pd/dq/synth- No matches -
Grepfor "streamFromCache" in/home/dwayne/pd/dq- No matches - Multiple other targeted searches for "fromCache", "fromCacheOrCreate", etc. - No matches
What I should have done
Simply used grep -r "streamFromCache" . from the start, which immediately found:
-
./src/main/java/com/pd/dq/synth/SynthMethodParams.javacontaining the actual method
Root cause analysis
- Over-optimization: I tried to be "smart" by searching in specific files/directories first to save context
- Tool limitations: The Grep tool seems to have issues with certain paths or may not be truly recursive
- Wrong assumptions: I assumed if it wasn't in the specific file, it might not exist at all
Lessons learned
- Always start with
grep -rfor any search - it's more reliable - Don't try to optimize searches by narrowing scope - just search everything
- The 132 lines of output that I might need to filter is better than missing the target entirely
- Focused searches fail more often than they succeed with the current tools
Pattern observed
This is a recurring problem where the search tools (Grep, Glob) seem to miss files that definitely exist. The native bash grep -r appears more reliable than the specialized search tools.
Issue 2: Failed to find findAllLightweightEbeanParallel method
Date: 2025-07-14
What happened
I searched for a method that should exist:
- Used Grep tool for "findAllLightweightEbeanParallel" in
/home/dwayne/pd/pd-data-acq/modules/ref/app/com/pd/data/ref/models- No matches - Was immediately ready to implement a new method
- The method actually existed at line 421 of URN.java
What I should have done
- Used Bash grep directly on the file:
grep -n parallel URN.java - Used broader search patterns with Grep tool: "parallel|Parallel|ebean|Ebean"
- Used Read tool to examine URN.java directly
- Tried multiple search approaches before assuming non-existence
Tool-specific observations
- Grep tool with exact string match on directory: Failed
- Read tool would have found it immediately (line 421)
- Bash grep likely would have worked with pattern matching
Issue 3: Failed to find claude-search-problems.md file
Date: 2025-07-14
What happened
While trying to document search problems, I exhibited the exact same problem:
- Used Read tool at
/home/dwayne/pd/dq/search_problems.md- File does not exist - Immediately prepared to create a new file
- Only after prompting did I use Glob tool for "*.md" and found
/home/dwayne/pd/dq/claude-search-problems.md
What I should have done
- After Read tool failed, immediately try Glob tool with patterns like "search.md"
- Use Bash ls:
ls *.md | grep -i search - Use Bash find:
find . -maxdepth 1 -name "*search*" -type f
Tool-specific observations
- Read tool with exact filename: Failed (wrong filename)
- Glob tool with pattern: Succeeded immediately
- Single tool failure led to abandoning search
Critical Pattern Analysis
My failure pattern with search tools:
- Use single tool (usually Grep tool or Read tool) with very specific input
- Tool returns no results
- Immediately assume target doesn't exist
- Start creating new file/code
- NEVER ask the user for help finding it
- NEVER ask "Should I create this?"
Better pattern should be:
- First tool fails → Try different tool
- Grep tool fails → Try Bash grep
- Read tool fails → Try Glob tool
- Exact match fails → Try pattern matching
- Specific path fails → Try broader path
- If multiple searches fail → Ask user: "I couldn't find X. Could you help me locate it?"
- Before creating anything → Ask user: "Should I create this new file/method?"
- Only create new content after explicit confirmation
Most Critical Missing Step
I completely skip the collaboration aspect. Instead of:
- Search fails → Create new
It should be:
- Search fails → Try alternative searches → Ask user for help → Get confirmation before creating
This would have prevented both duplicate implementations and wasted effort.
Potential Tool Issues
The Grep tool in particular seems unreliable:
- Often returns "No matches found" even when content exists
- May have issues with certain paths or recursive searching
- Bash grep appears more reliable than Grep tool
The search tools may have limitations that aren't immediately obvious, making multiple search strategies essential.
Issue 4: Failed to find testPipelineStages test
Date: 2025-07-16
What happened
User asked to run testPipelineStages test. I failed to find it:
- Used Grep tool for "testPipelineStages" in
/home/dwayne/pd/dq/src/test- No matches found - Used Grep tool for "Pipeline.*Test|pipeline.*performance" - No matches found
- Used Grep tool for "throughput|ThroughputTest" - No matches found
- Used Grep tool for "Throughput" in entire dq directory - No matches found
- Used LS tool to list performance directory and saw files but didn't check their contents
- Concluded the test didn't exist
What actually worked
User suggested using grep -r, which immediately found:
-
/home/dwayne/pd/dq/src/test/java/com/pd/dq/synth/performance/PipelineBottleneckTest.javacontainingtestPipelineStages
What I should have done
-
First step: Use Bash grep -r for recursive search:
grep -r "testPipelineStages" /home/dwayne/pd/dq/src/test/ - If Grep tool fails, immediately fallback to Bash grep -r
- When listing directories with LS tool, follow up by reading promising files
Root cause analysis
- Grep tool unreliability: The Grep tool repeatedly failed to find content that exists
- Over-reliance on single tool: I kept trying variations with Grep tool instead of switching to bash
- Incomplete investigation: I saw PipelineBottleneckTest.java in the directory listing but didn't check it
-
Path issues: The test was in a subdirectory (
synth/performance) that my searches may have missed
Key lesson
Always use grep -r with Bash tool as the primary search method. The Grep tool is consistently unreliable for finding files and content that definitely exist.
Issue 5: Failed to find MinimalSMPCTest.java
Date: 2025-07-19
What happened
User told me to run the MinimalSMPCTest to see what SMPs are generated for IP. I completely failed to find it:
- Used Grep tool for "minimalSMPC" in
/home/dwayne/pd/dq/src/test/java- No files found - Used Grep tool for "SMPCTest|SMPC.*Test" - No files found
- Used Grep tool for "SynthMethodParam|SMPTest|performStage1Analysis" - No files found
- Used Bash grep -r for "minimalSMPC|performStage1Analysis" - No output
- Used Bash grep -r for "SMP|SMPC" with filters - No output
- User had to explicitly tell me the path:
/home/dwayne/pd/dq/src/test/java/com/pd/dq/synth/MinimalSMPCTest.java
What actually worked
User pointed out I needed case insensitive search:
-
grep -ir "minimalsmpc" src/test/java/immediately found:-
src/test/java/com/pd/dq/synth/MinimalSMPCTest.java:public class MinimalSMPCTest {
-
What I should have done
-
Use case insensitive search by default:
grep -irinstead ofgrep -r - Try different case variations: "MinimalSMPC", "minimalsmpc", "minimal.*smpc"
- When user mentions a specific test name, check the exact path they might be referencing
-
Use ls or find commands:
find . -name "*SMPC*" -type f
Worst part
After failing to find MinimalSMPCTest, I just gave up and started analyzing a completely different test (PipelineBottleneckTest) instead of:
- Asking the user for help finding it
- Trying more search variations
- Using case insensitive search
Root cause analysis
- Case sensitivity assumption: I searched for "minimalSMPC" but the actual name was "MinimalSMPCTest"
- Gave up too quickly: Instead of trying variations, I just moved on to a different test
-
Didn't use all grep options: The
-iflag would have solved this immediately - Pattern mismatch: Searched for exact "minimalSMPC" when the class name had "Test" suffix
Critical lesson
Always use grep -ir (case insensitive recursive) as the default search, not just grep -r. Many Java class names have mixed case that won't match lowercase searches.
Issue 6: Failed to find guava dependency in build.gradle
Date: 2025-07-23
What happened
While discussing Multiset for tracking stats, I needed to check if Guava was in the dependencies:
- Used Grep tool for "guava" in
/home/dwayne/pd/dq/build.gradle- No matches found - Was about to suggest adding Guava as a new dependency
- User suggested using grep again
- Used Bash grep -i and immediately found:
implementation 'com.google.guava:guava:33.4.8-jre'
What actually worked
Simple bash grep with case insensitive flag:
-
grep -i guava /home/dwayne/pd/dq/build.gradlefound it immediately
What I should have done
- Use case insensitive search: The Grep tool seems to be case sensitive by default
- Try Bash grep as fallback: When Grep tool fails, immediately try bash grep
- Check different case variations: "guava", "Guava", "GUAVA"
Root cause analysis
- Case sensitivity strikes again: Similar to Issue 5, the Grep tool's case sensitivity caused a false negative
- Single tool reliance: I trusted the Grep tool's "No matches found" without trying alternatives
- build.gradle is a simple text file: Should be the easiest thing to search, yet Grep tool failed
Pattern reinforcement
The Grep tool continues to be unreliable. It has now failed to find:
- Methods in Java files (Issues 1, 2, 4)
- Test files (Issue 5)
- Simple text in build.gradle (Issue 6)
Recommendation: Always use Bash grep -i as the primary search method. The Grep tool cannot be trusted for accurate searches.
Issue 7: Failed to find createElement/createRoot in pdq2 codebase
Date: 2025-08-06
What happened
User mentioned we still have many createRoot/createElement usages. I completely failed to find them:
- Used Grep tool for "createElement|createRoot" in src/portal/user/UserManagement.ts - No matches found
- Used Grep tool for "createElement|createRoot" in src/services/UserManagement.ts - No matches found
- Used Grep tool for "createElement|createRoot" with glob "**/*.ts" - No files found
- Used Grep tool for "createElement|createRoot" with glob "**/*.tsx" - No files found
- Concluded there were no remaining usages
What actually worked
Simple bash grep immediately found them:
-
grep -r "createElement\|createRoot" src/ --include="*.ts" --include="*.tsx" | wc -lreturned 161 matches
What I should have done
-
First step: Use Bash grep -r immediately:
grep -r "createElement\|createRoot" src/ - Never trust Grep tool's "No files found": This is now the 7th documented failure
- Use multiple search methods: bash grep, find + xargs, ripgrep, etc.
Root cause analysis
- Glob pattern failure: The "**/*.ts" glob pattern completely failed to find any TypeScript files
- OR operator issue: The pipe character in "createElement|createRoot" may not work correctly in Grep tool
- False negative catastrophe: Grep tool reported zero matches when there were actually 161
- Complete tool failure: This is the worst failure yet - missing 161 occurrences
Critical observation
The Grep tool failed so badly that it made me think the migration was complete when we actually have 161 remaining instances to fix. This could have led to:
- Marking tasks as complete when they weren't
- Missing critical React 18 migration work
- Shipping broken code
Lesson learned
The Grep tool has serious reliability issues for searching code. While it may work sometimes, the documented failures show it's unreliable enough to cause significant problems. The bash grep found 161 matches where Grep tool found 0 in this case.
User's comment
"your searchtool is so broken. please search for a md (in different repo) about claude search problems. read it fully and update it with the example you just experienced. search/grep tool failed. grep -r was much better."
This perfectly summarizes the frustration with the Grep tool's consistent failures.
How to Reproduce
Test Case 1: Simple Search
# Create a test file
echo 'function createElement() { return null; }' > test.ts
# Grep tool fails (returns "No matches found")
Claude Code Grep tool: pattern="createElement" path="test.ts"
# Bash grep succeeds
grep "createElement" test.ts
Test Case 2: Glob Pattern Search
# In any TypeScript project with .ts files
# Grep tool fails (returns "No files found")
Claude Code Grep tool: pattern="function" glob="**/*.ts"
# Bash grep succeeds
grep -r "function" . --include="*.ts"
Test Case 3: OR Pattern Search
# Grep tool fails with OR patterns
Claude Code Grep tool: pattern="createElement|createRoot"
# Bash grep succeeds
grep -r "createElement\|createRoot" .
Environment
- Claude Code version: Current as of August 6, 2025
- Operating System: Linux (WSL2)
- Repository types tested: Java, TypeScript, React, Markdown
- File types tested: .java, .ts, .tsx, .md, .gradle
Expected Behavior
The Grep tool should find all occurrences of the searched pattern in the specified files/directories.
Actual Behavior
The Grep tool consistently returns "No matches found" or "No files found" even when the content exists.
Impact
- Critical: Prevents accurate code analysis
- Causes duplicate code creation
- Wastes significant developer time
- Leads to incorrect task completion status
Suggested Fix
- Replace Grep tool implementation with a wrapper around bash grep
- Or fix the underlying search algorithm
- Or remove the Grep tool and only use bash commands
- Add comprehensive tests for the search tools
Proposed Search Tool Enhancement
Acknowledgment
Search is inherently complex - it's not just grep -r. A production search tool needs to balance:
- Performance: Not searching unnecessary files (node_modules, build artifacts, etc.)
- Accuracy: Finding all relevant matches
- Context: Understanding project structure and conventions
- Usability: Providing clear, actionable results
Proposed Configuration Strategy
1. Two Distinct Search Modes
Exact Search (for imports, references, precise matches):
// Used when Claude reads: import { createElement } from 'react';
// Or when following a function call: this.findUserById(123)
exactSearch("createElement", {
caseSensitive: true,
wholeWord: false, // Still match createElement() or createElementWithProps
context: 'import' // Helps prioritize results
});
Fuzzy/Intelligent Search (for user queries, exploration):
// User says: "find the MinimalSMPCTest"
// Or: "where is findUserById defined?"
intelligentSearch("MinimalSMPCTest", {
caseSensitive: false,
splitCamelCase: true, // Searches: minimal, SMPC, test
splitUnderscore: true, // find_user_by_id → find, user, by, id
typoTolerance: 1, // Allow 1 character difference
stemming: true // findUser matches findUsers, finding, finder
});
2. Intelligent Query Processing
function processUserQuery(query: string): SearchStrategy {
// "findThisClassOrComponent" becomes:
const parts = splitIdentifier(query);
// ['find', 'This', 'Class', 'Or', 'Component']
// Generate search patterns (in priority order):
return {
exact: query, // findThisClassOrComponent
caseVariations: [
'FindThisClassOrComponent', // PascalCase
'find_this_class_or_component', // snake_case
'FIND_THIS_CLASS_OR_COMPONENT' // CONSTANT_CASE
],
partial: parts.filter(p => p.length > 2), // ['find', 'This', 'Class', 'Component']
fallback: parts.join('|') // find|This|Class|Component
};
}
// Search strategy:
// 1. Try exact match (case insensitive)
// 2. Try case variations
// 3. Try partial matches (must match 3+ parts)
// 4. Fallback to OR search if desperate
3. Context-Aware Search Modes
interface SearchConfig {
mode: 'exact' | 'fuzzy' | 'auto';
// exact: Precise matching for code navigation
// fuzzy: Intelligent matching for user queries
// auto: Detect based on context
strategy: 'direct' | 'smart' | 'exhaustive';
// direct: Use grep/ripgrep directly, no filtering
// smart: Apply intelligent filters based on project type
// exhaustive: Search everything, even typically excluded paths
filter: 'aggressive' | 'assertive' | 'minimal' | 'none';
// aggressive: Exclude all build, cache, dependency dirs
// assertive: Exclude node_modules, build, dist
// minimal: Only exclude .git, .svn
// none: No exclusions
caseSensitive: boolean; // Default: false for fuzzy, true for exact
followSymlinks: boolean; // Default: false
respectGitignore: boolean; // Default: true
}
4. Smart Word Splitting to Avoid Over-Broadness
function smartSplit(identifier: string): SearchPattern {
const parts = splitCamelCase(identifier);
// Don't split on common short words that would be too broad
const stopWords = ['or', 'and', 'is', 'in', 'on', 'at', 'to', 'for', 'of', 'a', 'the'];
const meaningfulParts = parts.filter(p =>
p.length > 2 && !stopWords.includes(p.toLowerCase())
);
// Different strategies based on part count
if (meaningfulParts.length <= 2) {
// Few parts: search for the whole thing
return identifier; // "UserList" stays as "UserList"
} else if (meaningfulParts.length <= 4) {
// Medium: require most parts
return `(${meaningfulParts.slice(0, -1).join('.*')})`; // "findUserById" → "find.*User.*by"
} else {
// Many parts: focus on key terms
const keyParts = meaningfulParts.filter(p => p.length > 3);
return keyParts.join('|'); // Only OR the substantial words
}
}
// Examples:
"MinimalSMPCTest" → "minimal.*SMPC.*test" (all parts connected)
"findThisClassOrComponent" → "find|class|component" (skip 'this', 'or')
"getUserByIdAndEmail" → "user.*email" (skip common connectors)
"XMLHttpRequest" → "XML.*Http.*Request" (preserve acronym grouping)
5. Avoiding False Positives
// Ranking results by relevance
interface SearchResult {
file: string;
line: number;
score: number; // How well it matches
}
function scoreMatch(query: string, match: string): number {
let score = 0;
// Exact match (case insensitive) = highest score
if (match.toLowerCase() === query.toLowerCase()) score += 100;
// Contains full query as substring = high score
if (match.toLowerCase().includes(query.toLowerCase())) score += 50;
// All parts present in order = medium score
const parts = splitCamelCase(query);
if (allPartsInOrder(parts, match)) score += 25;
// Some parts present = low score
const matchedParts = parts.filter(p => match.toLowerCase().includes(p.toLowerCase()));
score += (matchedParts.length / parts.length) * 10;
return score;
}
// Show high-scoring matches first, hide low-scoring noise
6. Interactive Search with Real-time Strategy Switching
interface InteractiveSearchSession {
// User can change strategy DURING search
currentStrategy: SearchStrategy;
searchScope: 'current-dir' | 'recursive' | 'specified-dir' | 'cross-repo' | 'approved-only';
askBeforeCreating: boolean;
failureThreshold: number; // After N failures, stop and ask
approvedLocations: string[]; // User-defined trusted paths
}
// Example interaction flow:
User: "Find MinimalSMPCTest"
Claude: "I'll search for MinimalSMPCTest with these permissions:
✓ Search in approved directories
✓ Use case-insensitive search if exact match fails
✓ Expand to full repository if not found
✓ Try multiple search strategies
✗ Will NOT create new files without asking
[Press Enter to proceed with all permissions, or 'c' to customize]"
User: [Enter]
Claude: [Proceeds with FULL autonomy within granted permissions]
// Search attempt 1: Exact match in current directory
Searching (exact, current-dir): "MinimalSMPCTest" ... No results
// User presses Shift+CapsLock → switches to fuzzy recursive
Searching (fuzzy, recursive): "minimal.*smpc.*test" ... No results
// User presses Shift+CapsLock → switches to case-insensitive
Searching (fuzzy, recursive, case-insensitive): Found 1 match!
→ src/test/java/com/pd/dq/synth/MinimalSMPCTest.java
// If no results after threshold:
Claude: "I couldn't find MinimalSMPCTest after trying:
- Exact match in current directory
- Fuzzy search recursively
- Case-insensitive search
Should I:
1. Search in a different directory? (specify path)
2. Try a different search term?
3. Create a new file with this name?
[Or press Shift+CapsLock to try a different search strategy]"
7. Search Mode Switching Hotkeys
// During any search operation, user can press:
const searchModes = {
'Shift+CapsLock': 'Cycle through search strategies',
'Ctrl+D': 'Toggle current directory only / recursive',
'Ctrl+I': 'Toggle case sensitive / insensitive',
'Ctrl+W': 'Toggle whole word / partial match',
'Ctrl+R': 'Switch to different repository',
'Ctrl+X': 'Expand search to cross-repo',
'Escape': 'Cancel search and ask for guidance'
};
// Visual feedback during search:
"🔍 Searching [fuzzy, recursive, case-insensitive] in ~/pd/dq
Press Shift+CapsLock to change mode
Press Escape to stop and get help
Attempt 1/3: exact match... ❌
Attempt 2/3: fuzzy match... ❌
Switching to case-insensitive...
Attempt 3/3: case-insensitive fuzzy... ✓ Found!"
8. Adaptive Failure Handling
class SearchSession {
private attempts = 0;
private maxAttempts = 3;
private triedStrategies: SearchStrategy[] = [];
async search(query: string) {
while (this.attempts < this.maxAttempts) {
// Check for user input BEFORE each search
const userOverride = await checkUserInput();
if (userOverride) {
this.currentStrategy = userOverride;
}
const results = await this.trySearch(query, this.currentStrategy);
if (results.length > 0) {
return results;
}
this.attempts++;
this.triedStrategies.push(this.currentStrategy);
// Auto-escalate strategy
this.currentStrategy = this.getNextStrategy();
}
// Failed all attempts - ASK before creating
return this.askUserForGuidance(query);
}
async askUserForGuidance(query: string) {
const response = await prompt(`
I couldn't find "${query}" after trying:
${this.triedStrategies.map(s => `- ${s.description}`).join('\n')}
What would you like me to do?
1. Try a different search term
2. Search in a specific directory
3. Create new file (requires confirmation)
4. Skip this search
`);
if (response === '3') {
const confirm = await prompt(`
Create new file "${query}"?
Location: ${this.suggestLocation(query)}
Type 'yes' to confirm:
`);
if (confirm === 'yes') {
return this.createNewFile(query);
}
}
}
}
9. Learning from User Corrections
// Track what strategies work for this project
interface SearchHistory {
successfulStrategies: Map<string, SearchStrategy>;
projectPatterns: {
testFiles: 'PascalCase' | 'snake_case' | 'kebab-case';
sourceFiles: 'PascalCase' | 'camelCase';
hasMonorepo: boolean;
commonMisspellings: Map<string, string>; // "minimalsmpc" → "MinimalSMPCTest"
};
}
// After successful search with user intervention:
searchHistory.record({
query: "minimalsmpc",
actualFile: "MinimalSMPCTest.java",
successfulStrategy: "case-insensitive-fuzzy",
userCorrection: true
});
// Next time, try case-insensitive first for this project
10. Approved Locations for Focused Searching
interface ApprovedLocations {
// User-defined locations where Claude should primarily search
primary: string[]; // Search these FIRST
secondary: string[]; // Search these if not found in primary
excluded: string[]; // NEVER search these (overrides everything)
// Project-specific patterns
testLocations: string[]; // Where tests live
sourceLocations: string[]; // Where source code lives
configLocations: string[]; // Where configs live
docsLocations: string[]; // Where documentation lives
}
// Example configuration (in .claude-search.json or CLAUDE.md):
{
"approvedLocations": {
"primary": [
"src/main/java/com/pd/dq",
"src/test/java/com/pd/dq",
"apps/pdq2/src"
],
"secondary": [
"modules/",
"libs/"
],
"excluded": [
"node_modules/",
"build/",
"dist/",
"target/",
".git/",
"legacy/",
"archive/",
"dq-old/"
],
"testLocations": [
"src/test/",
"tests/",
"spec/"
],
"sourceLocations": [
"src/main/",
"src/",
"lib/"
]
}
}
// Search strategy with approved locations:
async function searchWithApprovedLocations(query: string, context: SearchContext) {
// 1. Try primary locations first (fast, focused)
for (const location of approvedLocations.primary) {
const results = await search(query, { path: location });
if (results.length > 0) return results;
}
// 2. If searching for tests, check test locations
if (query.includes('Test') || query.includes('Spec')) {
for (const location of approvedLocations.testLocations) {
const results = await search(query, { path: location });
if (results.length > 0) return results;
}
}
// 3. Try secondary locations
for (const location of approvedLocations.secondary) {
const results = await search(query, { path: location });
if (results.length > 0) return results;
}
// 4. Only if explicitly requested, search outside approved locations
if (context.searchScope === 'exhaustive') {
console.log("Searching outside approved locations...");
return await search(query, {
path: '.',
exclude: approvedLocations.excluded
});
}
// 5. Ask user before searching unapproved locations
const shouldExpand = await prompt(
`Not found in approved locations. Search entire repository? (y/n)`
);
if (shouldExpand === 'y') {
return await search(query, { path: '.' });
}
}
11. Smart Location Detection
// Automatically detect and suggest approved locations based on:
function detectApprovedLocations(projectRoot: string): ApprovedLocations {
const detected = {
primary: [],
secondary: [],
excluded: [],
testLocations: [],
sourceLocations: []
};
// Check for common project structures
if (exists('package.json')) {
// Node/TypeScript project
detected.primary.push('src/', 'lib/');
detected.testLocations.push('test/', 'tests/', '__tests__/');
detected.excluded.push('node_modules/', 'dist/', 'build/');
}
if (exists('pom.xml') || exists('build.gradle')) {
// Java project
detected.primary.push('src/main/java/', 'src/test/java/');
detected.testLocations.push('src/test/');
detected.sourceLocations.push('src/main/');
detected.excluded.push('target/', 'build/', '.gradle/');
}
if (exists('requirements.txt') || exists('setup.py')) {
// Python project
detected.primary.push('src/', 'lib/');
detected.testLocations.push('tests/', 'test/');
detected.excluded.push('__pycache__/', '.venv/', 'venv/');
}
// Check for monorepo
if (exists('lerna.json') || exists('pnpm-workspace.yaml')) {
detected.primary.push('packages/*/src/', 'apps/*/src/');
detected.secondary.push('shared/', 'common/');
}
return detected;
}
12. Upfront Permission Granting - No Interruptions!
interface SearchPermissions {
// Grant ALL permissions upfront - no interruptions during search!
granted: {
useAllSearchStrategies: boolean; // Try exact, fuzzy, case-insensitive, etc.
searchOutsideApproved: boolean; // Can leave approved directories
crossRepositorySearch: boolean; // Can search other repos in ~/pd/
useAdvancedTools: boolean; // Can use find, grep, ripgrep, ag
createMissingFiles: 'never' | 'ask' | 'auto'; // File creation policy
maxSearchTime: number; // Stop after N seconds (0 = unlimited)
fallbackStrategies: 'all' | 'limited' | 'none';
};
// Pre-approved operations - no asking!
preApproved: {
directories: string[]; // Can search these without asking
commands: string[]; // Can run these commands
filePatterns: string[]; // Can search these file types
};
}
// Request permissions ONCE at the start:
async function requestSearchPermissions(task: string): SearchPermissions {
const defaultPermissions = {
granted: {
useAllSearchStrategies: true,
searchOutsideApproved: true,
crossRepositorySearch: false,
useAdvancedTools: true,
createMissingFiles: 'ask',
maxSearchTime: 60,
fallbackStrategies: 'all'
},
preApproved: {
directories: ['~/pd/dq', '~/pd/pdq2', '~/pd/pd-ui'],
commands: ['grep', 'find', 'rg', 'ag', 'ls'],
filePatterns: ['*.java', '*.ts', '*.tsx', '*.js', '*.py']
}
};
const response = await prompt(`
Task: ${task}
I need these permissions to complete your request:
[1] FULL AUTONOMY - Search everywhere, try everything (recommended)
[2] RESTRICTED - Only approved directories, basic search
[3] CUSTOM - Configure specific permissions
[Enter] Use defaults (Full autonomy for search, ask before creating)
Choose: `);
if (!response || response === '1') {
// FULL AUTONOMY - Never interrupt!
return {
...defaultPermissions,
granted: {
...defaultPermissions.granted,
searchOutsideApproved: true,
crossRepositorySearch: true,
maxSearchTime: 0, // Unlimited
fallbackStrategies: 'all'
}
};
}
return defaultPermissions;
}
13. One-Time Permission Templates
// Save common permission sets for reuse
const permissionTemplates = {
'full-autonomy': {
description: "Search everywhere, try everything, never interrupt",
granted: {
useAllSearchStrategies: true,
searchOutsideApproved: true,
crossRepositorySearch: true,
useAdvancedTools: true,
createMissingFiles: 'never', // Still won't create without asking
maxSearchTime: 0,
fallbackStrategies: 'all'
}
},
'focused-search': {
description: "Search only in current project, basic strategies",
granted: {
useAllSearchStrategies: false,
searchOutsideApproved: false,
crossRepositorySearch: false,
useAdvancedTools: false,
createMissingFiles: 'never',
maxSearchTime: 30,
fallbackStrategies: 'limited'
}
},
'exploration-mode': {
description: "For learning about codebase - read everything, create nothing",
granted: {
useAllSearchStrategies: true,
searchOutsideApproved: true,
crossRepositorySearch: true,
useAdvancedTools: true,
createMissingFiles: 'never',
maxSearchTime: 0,
fallbackStrategies: 'all'
}
}
};
// User can set default template in CLAUDE.md:
{
"defaultSearchPermissions": "full-autonomy",
"neverAskDuring": ["search", "read", "grep", "find"],
"alwaysAskFor": ["create", "delete", "modify"]
}
14. Batch Permission Requests
// When starting a complex task, request ALL permissions upfront:
async function startComplexTask(description: string) {
const permissions = await prompt(`
Task: ${description}
I'll need to:
- Search for multiple files and patterns
- Read various configuration files
- Possibly create or modify files
- Run build/test commands
Grant permissions for:
[ ] Search - Full autonomy to search anywhere
[ ] Read - Read any file without asking
[ ] Create - Create files without asking
[ ] Modify - Modify files without asking
[ ] Execute - Run commands without asking
Type permission letters (s,r,c,m,e) or 'all': `);
// User types: "sr" = search and read only
// User types: "all" = everything
// User types: "srcm" = everything except execute
// NOW PROCEED WITHOUT ANY INTERRUPTIONS!
return parsePermissions(permissions);
}
15. Never Interrupt Mid-Task
class NoInterruptSearcher {
constructor(private permissions: SearchPermissions) {}
async search(query: string) {
const strategies = this.permissions.granted.useAllSearchStrategies
? ['exact', 'fuzzy', 'case-insensitive', 'split-words', 'typo-tolerant']
: ['exact'];
for (const strategy of strategies) {
// NO PERMISSION CHECKS HERE - already granted!
const results = await this.tryStrategy(strategy, query);
if (results.length > 0) return results;
}
if (this.permissions.granted.searchOutsideApproved) {
// NO ASKING - just do it!
return await this.searchEverywhere(query);
}
// Only at the very END, if we failed everything:
if (this.permissions.granted.createMissingFiles === 'ask') {
return await this.askAboutCreating(query);
}
return []; // Failed, but don't interrupt!
}
}
16. Reusable Search Scripts and Permission Groups
// After a successful complex search:
interface SearchScriptPrompt {
async afterSuccessfulSearch(query: string, strategy: SearchStrategy) {
const shouldSave = await prompt(`
Found ${query} using: ${strategy.description}
Save this search as a reusable script for future use? (y/n)
This would let you run: claude-search find-minimal-test
`);
if (shouldSave === 'y') {
const scriptName = await prompt("Script name (or Enter for auto-name): ");
await this.saveSearchScript(scriptName || this.generateName(query));
}
}
async saveSearchScript(name: string) {
// Save to .claude/search/scripts/find-minimal-test.sh
const script = `#!/bin/bash
# Auto-generated search script
# Created: ${new Date().toISOString()}
# Purpose: Find ${this.query} efficiently
# Try strategies in order of past success
rg -i "minimal.*smpc.*test" src/test/ || \\
grep -ir "minimalsmpctest" . || \\
find . -name "*[Mm]inimal*[Tt]est*" -type f
`;
await writeFile(`.claude/search/scripts/${name}.sh`, script);
await chmod(`.claude/search/scripts/${name}.sh`, '755');
}
}
// Permission groups that can be shared and edited:
// .claude/search/permission-groups/exploration.json
{
"name": "exploration",
"description": "Full read access for learning the codebase",
"permissions": {
"search": ["**/*"],
"read": ["**/*"],
"create": [],
"modify": [],
"execute": ["grep", "find", "rg", "ag", "ls", "cat"]
},
"timeLimit": 0,
"strategies": "all"
}
// .claude/search/permission-groups/focused-dev.json
{
"name": "focused-dev",
"description": "Development in specific module",
"permissions": {
"search": ["src/", "tests/"],
"read": ["src/", "tests/", "*.md", "*.json"],
"create": ["src/", "tests/"],
"modify": ["src/", "tests/"],
"execute": ["npm", "jest", "gradle", "mvn"]
},
"excludeAlways": ["node_modules/", "build/", "dist/", ".git/"]
}
// User or Claude can edit these permission groups:
async function editPermissionGroup(name: string) {
const path = `.claude/search/permission-groups/${name}.json`;
const current = await readFile(path);
console.log(`Current ${name} permissions:`, current);
const updates = await prompt("Enter changes (JSON) or 'keep': ");
if (updates !== 'keep') {
await writeFile(path, {...current, ...JSON.parse(updates)});
console.log(`Updated ${name} permission group`);
}
}
17. Humble Acknowledgment and Implementation Notes
Important Note to Implementers:
This proposal represents ideas and user frustrations, not prescriptive solutions. The actual implementation will be significantly more complex than these examples suggest. Key considerations:
-
Tool Choice Flexibility:
-
ripgrep (rg)is often faster thangrep -r -
ag (the_silver_searcher)has excellent defaults -
fdis better thanfindfor file discovery - Each tool has strengths - implementation should be flexible
-
-
Real-world Complexity:
- Binary file handling
- Symbolic links and circular references
- Character encoding issues
- Network file systems
- Permission boundaries
- Memory constraints with large repositories
-
This is Inspiration, Not Prescription:
- These examples show the spirit of what users want
- Actual implementation will require proper engineering
- Edge cases will be numerous and complex
- Performance optimization will be critical
-
The Core User Needs (regardless of implementation):
- Reliability: Search should never completely fail
- Transparency: Show what's being searched and why
- Autonomy: Grant permissions once, not repeatedly
- Adaptability: Different searches need different strategies
- Reusability: Common searches should be scriptable
-
Alternative Approaches Welcome:
- Could use Language Server Protocol for code intelligence
- Could integrate with existing tools like
ctags,cscope - Could leverage git's search capabilities (
git grep) - Could use project-specific tools (Maven for Java, npm for Node)
The goal is not to dictate HOW to implement search, but to highlight that the current Grep tool's ~50% failure rate and frequent delays are problematic, and that search is a complex problem deserving a sophisticated, user-centric solution.
18. Initialization-time Index Building
On project init, the tool should:
# Build exclusion list from:
1. .gitignore patterns
2. Detected approved locations
2. .searchignore (if exists)
3. Common patterns for detected project type:
- Node: node_modules/, dist/, build/, coverage/
- Java: target/, out/, .gradle/, build/
- Python: __pycache__/, .venv/, venv/, .pytest_cache/
# Cache project structure
- File type distribution
- Directory depth analysis
- Symbolic link detection
3. Fallback Mechanism
async function searchWithFallback(pattern: string, options: SearchConfig) {
try {
// Try optimized search first
const results = await optimizedSearch(pattern, options);
if (results.length === 0 && options.strategy !== 'direct') {
// Fallback to direct grep if no results
console.log("No results with filters, trying direct search...");
return await directGrepSearch(pattern);
}
return results;
} catch (error) {
// Always fallback to grep on any error
return await directGrepSearch(pattern);
}
}
4. User Control Examples
// User could specify strategy in their query:
"Search for createElement --strategy=direct" // Skip all filtering
"Search for test --filter=none" // Search everything
"Search for TODO --exhaustive" // Include usually-excluded files
5. Transparency and Debugging
interface SearchResult {
matches: Match[];
metadata: {
strategy: string;
excludedPaths: string[];
searchedPaths: string[];
fallbackUsed: boolean;
timeMs: number;
command: string; // Actual command that was run
};
}
// Show what's happening:
"Searching with strategy: smart, filter: assertive
Excluding: node_modules/, dist/, build/
If no results, will fallback to direct grep"
Why This Matters
The current Grep tool appears to be trying to be "smart" but failing silently. By making the strategy explicit and configurable:
- Users can override when they know better
- Fallbacks prevent total failure
- Transparency builds trust - users see what's being excluded
- Performance when possible - smart defaults for common cases
- Reliability when needed - direct mode always available
Implementation Priority
-
Immediate: Add
--strategy=directoption that just wraps bash grep - Short-term: Implement fallback mechanism
- Long-term: Build smart exclusion lists and project-aware searching
This acknowledges that search is complex while ensuring it's never completely broken like the current state.