Code Search skill for Claude Code

Find every usage, and prove you found them all. Claude picks the right search tool for the question, from ripgrep with types, globs and word boundaries to ast-grep for call shapes, git grep for history and LSP for type-aware references. It searches every spelling, includes config, templates, SQL and infra, follows re-exports and dynamic access, cross-checks counts, and reports results with file and line evidence plus an explanation of false positives.

  • namecode-search
  • size150 lines · ≈1,650 tokens
  • files1
  • licenseMIT
  • updated

What Claude sees before loading it

This description is the only part of the skill that sits in context all the time. Claude reads it to decide when to load the full playbook below.

description: Finds anything in a large codebase fast and completely by choosing the right tool per question, ripgrep with type filters, globs, fixed strings, word boundaries and multiline, ast-grep structural patterns, git grep across history and branches, LSP or ctags symbol navigation, and file-name search, then narrowing iteratively and proving completeness before acting on the results. Use when the user asks where something is defined or used, to find all call sites before a change, to locate the code behind an error message, URL, or config key, or when a grep returns too much or too little.

Try asking

  • Find every call site of fetchUser before I change its signature
  • Where is the code that produces this error: 'could not acquire lock'?
  • Find all useEffect hooks with an empty dependency array in the web app

Use it when

  • You need all usages of a symbol, key, or route before changing it
  • An error message, log line, or URL must be traced to code
  • A text search returns too many false positives
  • You are working in a large or unfamiliar codebase

Skip it when

  • You want an overall map of an unfamiliar repo — use codebase-onboarding instead
  • You already found the call sites and need to rewrite them all — use codemods instead

How to check the result

The answer lists file:line evidence, and the completeness checklist is ticked, including spellings, non-code files, ignored directories and an LSP or second-tool count comparison.

The common mistake it prevents

Searching only src/ for one spelling, then renaming and breaking config files, SQL, templates and scripts that referenced the old name in another case style.

~/.claude/skills/code-search/SKILL.md

--- name: code-search description: Finds anything in a large codebase fast and completely by choosing the right tool per question, ripgrep with type filters, globs, fixed… license: MIT ---

A search is done when you can state the answer with file:line evidence and you know it is complete: every spelling, layer, and file type where the thing can appear was covered, and the false positives were explained. For “find all usages before changing X”, completeness matters more than speed.

Pick the tool

QuestionToolExample
Where is this text/identifier?ripgreprg -n -w 'fetchUser' src
Where is this exact string with regex chars?ripgrep fixed stringrg -nF 'user?.id ?? 0'
Calls with a certain shape (args, nesting)ast-grepast-grep run -l ts -p 'fetchUser($A, { cache: true })'
Where is it defined / all references, type-awareLSP (IDE, code-intelligence plugin)Go to definition / find references
When did it appear, on which branch?git grep / git log -Sgit grep -n 'flagName' $(git rev-list --all -n 200)
Which file is called something like X?file searchrg --files | rg -i 'invoice.*service'
Code behind an error message or log lineripgrep on a stable fragmentrg -nF 'could not acquire lock'
Code behind a URL/routeripgrep on path segmentsrg -n "['\"]/api/v1/invoices"

Workflow

- [ ] 1. Write the question and the target names in every plausible spelling
- [ ] 2. Start broad with counts, then narrow by path/type/word
- [ ] 3. Switch to structural search when text matches are noisy
- [ ] 4. Cover non-code: config, templates, SQL, infra, docs, tests
- [ ] 5. Prove completeness (checklist below); explain false positives
- [ ] 6. Report results with file:line, grouped by meaning

1. Spellings

Identifiers change shape across layers. For a concept invoice status: invoiceStatus, InvoiceStatus, invoice_status, INVOICE_STATUS, invoice-status, "invoiceStatus" in JSON, invoice.status in templates. One regex covers most:

rg -n -i 'invoice[_-]?status' .

2. Broad, then narrow

rg -c 'fetchUser' | sort -t: -k2 -nr | head      # where are the hits concentrated?
rg -n -w 'fetchUser' -t ts -g '!**/*.test.ts' src # whole word, TS only, no tests
rg -n 'fetchUser\(' -g 'src/server/**'            # call sites in one area
rg -l 'fetchUser' | xargs rg -l 'cache'           # files containing both terms

ripgrep essentials:

FlagEffect
-n / -l / -cLine numbers / file names only / counts per file
-wWhole-word match
-FFixed string, no regex
-i / -SCase-insensitive / smart case
-t ts / -T tsInclude / exclude a file type (rg --type-list)
-g 'glob' / -g '!glob'Include / exclude paths
-A 3 -B 3 / -C 3Context lines
-U --multiline-dotallPatterns spanning lines
--hidden / -uuSearch dotfiles / also ignore .gitignore rules
--pcre2Lookarounds and backreferences
-oPrint only the matched part (pipe to sort | uniq -c)

ripgrep respects .gitignore by default. Generated or vendored code that is ignored will not appear: use -uu when the answer might be there (built bundles, vendor/).

3. Structural search with ast-grep

Use when text search matches comments, strings, or similar names, or when the shape of the call matters:

ast-grep run -l ts -p 'useEffect($FN, [])'                    # effects with empty deps
ast-grep run -l ts -p 'await $X.findMany({ $$$ })' src         # Prisma calls, any args
ast-grep run -l py -p 'requests.$METHOD($URL, verify=False)'   # insecure requests
ast-grep run -l go -p 'if err != nil { return nil }'           # swallowed errors

$X matches one node; $$$ matches zero or more. Add --json for machine-readable output. For repeated checks, write a rule file and run ast-grep scan.

4. Beyond source code

Usages hide in: YAML/JSON config, .env.example, SQL migrations and raw queries, templates (Jinja, ERB, Handlebars), GraphQL schemas, OpenAPI specs, infra (Terraform, Helm, Kubernetes), CI workflows, docs, fixtures, feature-flag definitions, and other repositories. Search without type filters at least once:

rg -n --hidden -g '!.git' -g '!node_modules' -g '!dist' 'invoice[_-]?status'

Dynamic references defeat every text search: string concatenation ("get" + name), reflection, getattr, DI by name, routes built from config. Search for the fragments and for the registry that builds them.

5. Completeness checklist

  • All spellings and casings searched
  • Tests, config, templates, SQL, infra, and docs included
  • Ignored and generated directories considered (-uu)
  • Re-exports and aliases followed (export { fetchUser as getUser }, import * as api)
  • Dynamic access patterns searched
  • Other repos or packages that depend on this one considered
  • For type-aware languages, LSP “find references” count compared with grep count

If grep and LSP disagree, investigate the difference before acting; it is usually an alias, a dynamic call, or a stale index.

Large repos and history

git grep -n 'oldFlag' main -- '*.ts'               # a branch without checking it out
git log -S'oldFlag' --oneline --all                # commits adding/removing it
git ls-files | rg -i 'billing' | head              # tracked files only, very fast

For very large monorepos, scope by package first (rg ... packages/billing), and use --max-columns 200 to avoid minified lines flooding output.

Gotchas

  • Unanchored patterns overmatch (id matches width); use -w or \b.
  • Regex special characters in literal searches (., ?, () silently change meaning; use -F.
  • Shell quoting: single-quote patterns; $ in double quotes is expanded by the shell.
  • Searching only src/ misses scripts, tests, and config that will break on rename.
  • Reading hundreds of matches into context wastes tokens: count first (-c), then open the files that matter, or delegate a wide sweep to a subagent and keep its summary.

Output

**Question:** all call sites of fetchUser() before changing its signature
**Result:** 14 call sites in 9 files (LSP references: 14, rg -w: 17; 3 extra were a comment, a test name, a string in docs)
- src/server/routes/users.ts:42, :88 — direct calls
- src/web/hooks/useUser.ts:19 — via re-export `getUser` (src/api/index.ts:7)
- scripts/backfill.ts:33 — script, not covered by tests
**Also found:** dynamic call via handlers["fetch" + entity] in src/server/registry.ts:51 (covers User)
**Completeness:** spellings, tests, config, scripts, -uu search done; no other repos depend on this package

About this skill

Written and maintained by the Claude Skills editors to the same editorial standard as every skill in the directory: a selective trigger, a real workflow, known pitfalls and a verification step. First published , last updated . MIT licensed.

esc