The Two Search Tools
| Tool | What it searches | Returns | Hard limit |
|---|---|---|---|
| Glob | File names (glob pattern) | Array of file paths sorted by mtime | 100 files |
| Grep | File contents (regex) | Paths, lines, or counts | 250 lines/files |
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.
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)
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)
}
}
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.
Grep's Three Output Modes
Default. Retorna apenas file paths. Low token cost.
rg -l
Sorted by mtime (most recent first)
Retorna matching lines com context.
rg (no flag)
Supports -n, -B/-A/-C, multiline
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())
}
}
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.
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 }
}
- 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.
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).
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.
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.
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.
Performance Architecture
- 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')
}
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.
- 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
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.
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
Glob e ripgrep com --files. Nao ha JavaScript glob library. Ambas delegam para o mesmo binario ripgrep.
Tres output modes = tres estrategias ripgrep. files_with_matches (-l), content (no flag), count (-c). Escolher o modo correto evita trabalho desnecessario.
Head-limiting antes de path relativization e otimizacao deliberada. head_limit=0 e o escape hatch.
Binary resolution e three-tier e memoized. system -> embedded -> builtin. Seguranca: "rg" nao path resolvido.
EAGAIN retry scoped a single call. Persistir global causou regressoes. -j 1 so para aquela chamada.
mtime sort e o sinal de relevancia default. Arquivos recentemente editados aparecem primeiro em files_with_matches.