claude-code icon indicating copy to clipboard operation
claude-code copied to clipboard

[BUG] Claude Code Search / Grep Tool Critical Bug Report

Open DwayneSmurdon opened this issue 6 months ago • 6 comments

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 -r as 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

  1. Grep tool returns "No matches found" or "No files found"
  2. Bash grep -r immediately finds the content
  3. Content definitively exists at the searched location
  4. 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:

  1. Grep for "streamFromCache" in /home/dwayne/pd/dq/src/main/java/com/pd/dq/synth/SynthMethodParams.java - No matches
  2. Grep for "streamFromCache" in /home/dwayne/pd/dq/src/main/java/com/pd/dq/synth - No matches
  3. Grep for "streamFromCache" in /home/dwayne/pd/dq - No matches
  4. 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.java containing the actual method

Root cause analysis

  1. Over-optimization: I tried to be "smart" by searching in specific files/directories first to save context
  2. Tool limitations: The Grep tool seems to have issues with certain paths or may not be truly recursive
  3. Wrong assumptions: I assumed if it wasn't in the specific file, it might not exist at all

Lessons learned

  • Always start with grep -r for 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:

  1. Used Grep tool for "findAllLightweightEbeanParallel" in /home/dwayne/pd/pd-data-acq/modules/ref/app/com/pd/data/ref/models - No matches
  2. Was immediately ready to implement a new method
  3. The method actually existed at line 421 of URN.java

What I should have done

  1. Used Bash grep directly on the file: grep -n parallel URN.java
  2. Used broader search patterns with Grep tool: "parallel|Parallel|ebean|Ebean"
  3. Used Read tool to examine URN.java directly
  4. 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:

  1. Used Read tool at /home/dwayne/pd/dq/search_problems.md - File does not exist
  2. Immediately prepared to create a new file
  3. 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

  1. After Read tool failed, immediately try Glob tool with patterns like "search.md"
  2. Use Bash ls: ls *.md | grep -i search
  3. 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:

  1. Use single tool (usually Grep tool or Read tool) with very specific input
  2. Tool returns no results
  3. Immediately assume target doesn't exist
  4. Start creating new file/code
  5. NEVER ask the user for help finding it
  6. NEVER ask "Should I create this?"

Better pattern should be:

  1. First tool fails → Try different tool
  2. Grep tool fails → Try Bash grep
  3. Read tool fails → Try Glob tool
  4. Exact match fails → Try pattern matching
  5. Specific path fails → Try broader path
  6. If multiple searches fail → Ask user: "I couldn't find X. Could you help me locate it?"
  7. Before creating anything → Ask user: "Should I create this new file/method?"
  8. 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:

  1. Used Grep tool for "testPipelineStages" in /home/dwayne/pd/dq/src/test - No matches found
  2. Used Grep tool for "Pipeline.*Test|pipeline.*performance" - No matches found
  3. Used Grep tool for "throughput|ThroughputTest" - No matches found
  4. Used Grep tool for "Throughput" in entire dq directory - No matches found
  5. Used LS tool to list performance directory and saw files but didn't check their contents
  6. 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.java containing testPipelineStages

What I should have done

  1. First step: Use Bash grep -r for recursive search: grep -r "testPipelineStages" /home/dwayne/pd/dq/src/test/
  2. If Grep tool fails, immediately fallback to Bash grep -r
  3. When listing directories with LS tool, follow up by reading promising files

Root cause analysis

  1. Grep tool unreliability: The Grep tool repeatedly failed to find content that exists
  2. Over-reliance on single tool: I kept trying variations with Grep tool instead of switching to bash
  3. Incomplete investigation: I saw PipelineBottleneckTest.java in the directory listing but didn't check it
  4. 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:

  1. Used Grep tool for "minimalSMPC" in /home/dwayne/pd/dq/src/test/java - No files found
  2. Used Grep tool for "SMPCTest|SMPC.*Test" - No files found
  3. Used Grep tool for "SynthMethodParam|SMPTest|performStage1Analysis" - No files found
  4. Used Bash grep -r for "minimalSMPC|performStage1Analysis" - No output
  5. Used Bash grep -r for "SMP|SMPC" with filters - No output
  6. 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

  1. Use case insensitive search by default: grep -ir instead of grep -r
  2. Try different case variations: "MinimalSMPC", "minimalsmpc", "minimal.*smpc"
  3. When user mentions a specific test name, check the exact path they might be referencing
  4. 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

  1. Case sensitivity assumption: I searched for "minimalSMPC" but the actual name was "MinimalSMPCTest"
  2. Gave up too quickly: Instead of trying variations, I just moved on to a different test
  3. Didn't use all grep options: The -i flag would have solved this immediately
  4. 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:

  1. Used Grep tool for "guava" in /home/dwayne/pd/dq/build.gradle - No matches found
  2. Was about to suggest adding Guava as a new dependency
  3. User suggested using grep again
  4. 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.gradle found it immediately

What I should have done

  1. Use case insensitive search: The Grep tool seems to be case sensitive by default
  2. Try Bash grep as fallback: When Grep tool fails, immediately try bash grep
  3. Check different case variations: "guava", "Guava", "GUAVA"

Root cause analysis

  1. Case sensitivity strikes again: Similar to Issue 5, the Grep tool's case sensitivity caused a false negative
  2. Single tool reliance: I trusted the Grep tool's "No matches found" without trying alternatives
  3. 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:

  1. Used Grep tool for "createElement|createRoot" in src/portal/user/UserManagement.ts - No matches found
  2. Used Grep tool for "createElement|createRoot" in src/services/UserManagement.ts - No matches found
  3. Used Grep tool for "createElement|createRoot" with glob "**/*.ts" - No files found
  4. Used Grep tool for "createElement|createRoot" with glob "**/*.tsx" - No files found
  5. 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 -l returned 161 matches

What I should have done

  1. First step: Use Bash grep -r immediately: grep -r "createElement\|createRoot" src/
  2. Never trust Grep tool's "No files found": This is now the 7th documented failure
  3. Use multiple search methods: bash grep, find + xargs, ripgrep, etc.

Root cause analysis

  1. Glob pattern failure: The "**/*.ts" glob pattern completely failed to find any TypeScript files
  2. OR operator issue: The pipe character in "createElement|createRoot" may not work correctly in Grep tool
  3. False negative catastrophe: Grep tool reported zero matches when there were actually 161
  4. 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

  1. Replace Grep tool implementation with a wrapper around bash grep
  2. Or fix the underlying search algorithm
  3. Or remove the Grep tool and only use bash commands
  4. 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:

  1. Tool Choice Flexibility:

    • ripgrep (rg) is often faster than grep -r
    • ag (the_silver_searcher) has excellent defaults
    • fd is better than find for file discovery
    • Each tool has strengths - implementation should be flexible
  2. Real-world Complexity:

    • Binary file handling
    • Symbolic links and circular references
    • Character encoding issues
    • Network file systems
    • Permission boundaries
    • Memory constraints with large repositories
  3. 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
  4. 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
  5. 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:

  1. Users can override when they know better
  2. Fallbacks prevent total failure
  3. Transparency builds trust - users see what's being excluded
  4. Performance when possible - smart defaults for common cases
  5. Reliability when needed - direct mode always available

Implementation Priority

  1. Immediate: Add --strategy=direct option that just wraps bash grep
  2. Short-term: Implement fallback mechanism
  3. 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.

DwayneSmurdon avatar Aug 06 '25 16:08 DwayneSmurdon