Trilha 2 2.4 Search Tools
MODULO 2.4

🔍 Search Tools: Glob & Grep

Ambas delegam para ripgrep: Glob busca nomes de arquivo, Grep busca conteudo. Tres output modes, pagination e resolucao de binario em tres camadas.

~45
Minutos
2
Tools
3
Output Modes
250
Default Limit
1

The Two Search Tools

Tool What it searches Returns Hard limit
GlobFile names (glob pattern)Array of file paths sorted by mtime100 files
GrepFile contents (regex)Paths, lines, or counts250 lines/files
MESMO NOME NA UI

Ambas ferramentas compartilham o user-facing name "Search". Na UI do terminal, nem "Glob" nem "Grep" aparece -- apenas "Search". Os nomes distintos existem na camada API/schema onde o modelo decide qual chamar.

2

Glob Delegates to ripgrep

Apesar do nome, esta tool NAO usa Node's fs.glob, fast-glob ou qualquer JavaScript glob library. Delega inteiramente para ripgrep via utils/glob.ts.

const args = [
  '--files',        // list files instead of searching content
  '--glob', searchPattern,
  '--sort=modified',
  ...(noIgnore ? ['--no-ignore'] : []),
  ...(hidden  ? ['--hidden']    : []),
]

const allPaths = await ripGrep(args, searchDir, abortSignal)
INSIGHT CHAVE

ripgrep com --files --glob faz glob traversal de alta performance. O directory walker multi-threaded do ripgrep e significativamente mais rapido que implementacoes fs-based do Node em codebases grandes.

Absolute-Path Decomposition

export function extractGlobBaseDirectory(pattern: string): {
  baseDir: string; relativePattern: string
} {
  // Find first glob special char: * ? [ {
  const match = pattern.match(/[\*?\[{\]/)
  if (!match || match.index === undefined) {
    return { baseDir: dirname(pattern), relativePattern: basename(pattern) }
  }
  const staticPrefix = pattern.slice(0, match.index)
  const lastSep = Math.max(staticPrefix.lastIndexOf('/'), staticPrefix.lastIndexOf(sep))
  return {
    baseDir: staticPrefix.slice(0, lastSep),
    relativePattern: pattern.slice(lastSep + 1)
  }
}
POR QUE IGNORAR .gitignore POR DEFAULT?

Claude precisa ver build artifacts, generated files e node_modules structure para responder perguntas como "esta dependencia esta instalada?" ou "quais arquivos foram emitidos pelo build?" Respeitar gitignore por default esconderia contexto demais.

3

Grep's Three Output Modes

files_with_matches

Default. Retorna apenas file paths. Low token cost.

rg -l

Sorted by mtime (most recent first)

content

Retorna matching lines com context.

rg (no flag)

Supports -n, -B/-A/-C, multiline

count

Retorna match counts per file.

rg -c

Format: filename:N

// Output mode dispatch
if (output_mode === 'files_with_matches') { args.push('-l') }
else if (output_mode === 'count') { args.push('-c') }
// content mode: no flag needed

// Context flags (-C supersedes -B/-A)
if (output_mode === 'content') {
  if (context !== undefined) args.push('-C', context.toString())
  else {
    if (context_before !== undefined) args.push('-B', context_before.toString())
    if (context_after  !== undefined) args.push('-A', context_after.toString())
  }
}
MTIME SORT = RELEVANCE SIGNAL

Em files_with_matches mode, resultados sao sorted by modification time (most recent first). A premissa: arquivos que voce acabou de editar sao mais relevantes para a tarefa atual. Test mode usa filename sort para VCR fixtures deterministicos.

4

Pagination: head_limit e offset

Ambas ferramentas suportam head_limit e offset como um pipeline tail -n +N | head -N.

function applyHeadLimit<T>(items: T[], limit: number | undefined, offset: number = 0) {
  // Explicit 0 = unlimited escape hatch
  if (limit === 0) return { items: items.slice(offset), appliedLimit: undefined }

  const effectiveLimit = limit ?? 250    // DEFAULT_HEAD_LIMIT
  const sliced = items.slice(offset, offset + effectiveLimit)

  // appliedLimit is ONLY set when truncation occurred
  const wasTruncated = items.length - offset > effectiveLimit
  return { items: sliced, appliedLimit: wasTruncated ? effectiveLimit : undefined }
}
3 DESIGN DECISIONS
  • limit=0 e o escape hatch unlimited. Schema avisa "use sparingly -- large result sets waste context."
  • appliedLimit so setado quando truncation ocorreu. Modelo so ve hint de pagination quando ha mais paginas.
  • Head-limiting antes de path relativization. Evita processar milhares de linhas que serao descartadas.
20KB PERSIST THRESHOLD

Tool results acima de 20KB sao persistidos em disco ao inves de mantidos no prompt. O default de 250 linhas mantem resultados bem abaixo deste threshold (~6-24K tokens/grep-heavy session).

5

ripgrep Binary Resolution

Toda invocacao de Grep e Glob chama ripgrep. Qual binario roda depende de uma chain de resolucao de tres modos, avaliada uma vez por processo e memoizada.

flowchart TD A["Check USE_BUILTIN_RIPGREP"] --> B{"Falsy?"} B -->|Yes| C{"rg on PATH?"} C -->|Yes| D["system mode\ncommand: 'rg'\nSECURITY: name not path"] C -->|No| E{"Bundled Bun?"} B -->|No| E E -->|Yes| F["embedded mode\nprocess.execPath\nargv0='rg'"] E -->|No| G["builtin mode\nvendor/ripgrep/arch-platform/rg"] style D fill:#1e293b,stroke:#3b82f6,color:#e2e8f0 style F fill:#1e293b,stroke:#10b981,color:#e2e8f0 style G fill:#1e293b,stroke:#f59e0b,color:#e2e8f0
SEGURANCA: PATH HIJACKING

Quando usando system ripgrep, o comando e spelled "rg" (nao o path resolvido) para prevenir execucao de ./rg.exe malicioso no diretorio atual via PATH hijacking.

macOS CODE-SIGNING

Em macOS, o binario rg vendored vem apenas com linker signature. Na primeira use, codesignRipgrepIfNecessary() re-assina com codesign --sign - (ad-hoc) e strip quarantine xattr. So roda para builtin mode, uma vez por processo.

6

Performance Architecture

TIMEOUTS PLATFORM-AWARE
  • 20 seconds em plataformas standard
  • 60 seconds em WSL (3-5x file I/O penalty)
  • Override via CLAUDE_CODE_GLOB_TIMEOUT_SECONDS
  • Kill escalation: SIGTERM primeiro, SIGKILL apos 5s

EAGAIN Retry com Single-Thread Fallback

if (!isRetry && isEagainError(stderr)) {
  logForDebugging(`rg EAGAIN error, retrying with -j 1`)
  ripGrepRaw(args, target, abortSignal, handleResult, true) // singleThread
  return
}

function isEagainError(stderr: string): boolean {
  return stderr.includes('os error 11') ||
         stderr.includes('Resource temporarily unavailable')
}
RETRY SCOPED, NAO GLOBAL

O retry usa -j 1 apenas para aquela call e restaura multi-threaded imediatamente. Uma versao anterior persistia single-threaded mode globalmente, causando timeouts em repos grandes onde EAGAIN era erro transiente de startup.

OTIMIZACOES ADICIONAIS
  • Buffer cap 20MB: monorepos com 200k+ files podem produzir stdout maior que default 1MB
  • Path relativization: caminhos absolutos custam mais tokens que relativos. Em 250 files, salva centenas de tokens
  • Line length cap 500 chars: previne base64, minified JS, long single-line content de flooding output
  • Concurrent safety: ambas declararam isConcurrencySafe() = true. Read-only, sem shared mutable state
7

Pattern Safety e UNC Path Security

Leading-Dash Problem

// If pattern starts with dash, use -e flag
if (pattern.startsWith('-')) {
  args.push('-e', pattern)
} else {
  args.push(pattern)
}

O flag -e diz ao ripgrep "o que segue e um pattern, nao um flag." Classe de vulnerabilidade de injection comum em tools que fazem shell-out.

UNC PATH -- NTLM CREDENTIAL LEAK

Ambas Glob e Grep skipam stat() para paths comecando com \\ ou // (UNC paths no Windows). Um stat() em UNC path triggera SMB authentication handshake que pode leakar NTLM hashes para servidor malicioso.

VCS Directory Exclusions

const VCS_DIRECTORIES_TO_EXCLUDE = [
  '.git', '.svn', '.hg', '.bzr', '.jj', '.sl',
] as const
// Git, Subversion, Mercurial, Bazaar, Jujutsu, Sapling

🎯 Resumo e Takeaways

1

Glob e ripgrep com --files. Nao ha JavaScript glob library. Ambas delegam para o mesmo binario ripgrep.

2

Tres output modes = tres estrategias ripgrep. files_with_matches (-l), content (no flag), count (-c). Escolher o modo correto evita trabalho desnecessario.

3

Head-limiting antes de path relativization e otimizacao deliberada. head_limit=0 e o escape hatch.

4

Binary resolution e three-tier e memoized. system -> embedded -> builtin. Seguranca: "rg" nao path resolvido.

5

EAGAIN retry scoped a single call. Persistir global causou regressoes. -j 1 so para aquela chamada.

6

mtime sort e o sinal de relevancia default. Arquivos recentemente editados aparecem primeiro em files_with_matches.

2.3 File Tools 2.5 MCP Integration