Core insight:
rgis gitignore-aware, parallel, and Unicode-correct by default. Reaching forgrep -rorfind | xargs grepthrows all three away. Default torg.
⚠️ Critical Constraints
- Default to
rg, nevergrep -rorfind … -exec grep. Why:rgwalks the tree in parallel, skips.git/,node_modules/, and everything in.gitignoreautomatically, and is typically 5–50× faster.# WRONG — slow, scans ignored dirs, no parallelism grep -rn "parseConfig" . find . -name '*.go' -exec grep -l "parseConfig" {} + # CORRECT rg "parseConfig" rg -t go "parseConfig"
-
Filter by type with
-t/--type, not by pipingfindinto rg. Why:-t gois resolved internally against rg's built-in type map; spawningfindfirst serializes the walk and defeats rg's own ignore logic.# WRONG find . -name '*.py' | xargs rg "TODO" # CORRECT rg -t py "TODO" -
Quote the pattern; escape regex metacharacters or use
-F. Why: an unquoted pattern is glob-expanded by the shell, and.([*$are regex operators. Literal searches must use-F/--fixed-strings.# WRONG — shell expands *, regex eats the dots and parens rg config.get(*) # CORRECT — literal search for the exact text rg -F 'config.get(*)' -
Never use
-U/--multilineas a reflex. It buffers whole files and is far slower. Why: most "find the function" searches are single-line; only reach for-Uwhen the match genuinely spans newlines. -
For agent/tool consumption, emit
--json, not human output. Why: human output is heading-grouped and color-coded;--jsonis one stable NDJSON object per event, parseable without regex-scraping.
Why This Exists
Agents repeatedly default to grep -rn, find -exec, or unfiltered rg over the
whole repo, then wait on (or drown in) results. The cost is real: scanning .git/
and vendored trees, missing the -t filter that would have narrowed 10k files to
40, or dumping thousands of lines into context when -l / -c would have answered
the question in one line. This contract encodes the small set of flags and the
decision tree that turn search from a bottleneck into a reflex. It is a reference
you grep in place, not a workflow you execute top-to-bottom.
Quick Start
rg "needle" # recursive, gitignore-aware, line-numbered, grouped
rg -t go "func NewServer" # only Go files
rg -i "deadline" # case-insensitive
rg -S "Deadline" # smart-case: literal-lower → insensitive, has-upper → sensitive
rg -l "import requests" # files-with-matches only (cheap, great for "where is X used")
rg -c "TODO" # per-file match counts
rg -A3 -B1 "panic(" # 3 lines after, 1 before each hit
rg -o '\bv\d+\.\d+\.\d+\b' # print only the matched substring (extraction)
rg -F 'a[b].c(' src/ # literal search, no regex interpretation
rg --json "needle" | jq -c . # machine-readable NDJSON for tooling
Flag Reference (the ones that matter)
| Need | Flag | Note |
|---|---|---|
| Limit by language/type | -t TYPE / --type-not TYPE |
rg --type-list to see types; --type-add for custom |
| Limit by path pattern | -g GLOB / --iglob |
-g '!**/test/**' to exclude; repeatable |
| Context | -A N / -B N / -C N |
after / before / both |
| Files only (cheap) | -l / --files-with-matches |
answer "where" without dumping bodies |
| Counts | -c / --count-matches |
-c = lines/file; --count-matches = total hits |
| Literal (no regex) | -F / --fixed-strings |
always use for strings with .([*$ |
| Whole word | -w / --word-regexp |
avoids id matching valid |
| Case | -i / -s / -S |
insensitive / sensitive / smart |
| Extract substring | -o / --only-matching |
print match, not whole line |
| Cross-line match | -U / --multiline (+--multiline-dotall) |
slow; only when match spans \n |
| Search ignored files | --no-ignore / --hidden / -uu |
opt back in to .gitignored / dotfiles |
| Bound the search | --max-count N / --max-depth N |
cap hits per file / dir depth |
| Replace in output | -r REPLACEMENT |
preview substitutions (does NOT write files) |
| Perl regex | -P / --pcre2 |
lookaround/backrefs; slower, use only when needed |
| Stable order | --sort path |
deterministic output (disables parallelism) |
| Machine output | --json |
NDJSON event stream — parse this, not text |
Strategy Decision Tree
What do you need?
├── "Which files contain X?" → rg -l "X" (cheapest)
├── "How many / how often?" → rg -c "X" or --count-matches
├── "Where is X, with context?" → rg -C3 "X" (then narrow with -t/-g)
├── "Only in Go/Py/etc.?" → rg -t go "X"
├── "Exclude tests/vendor?" → rg -g '!**/{test,vendor}/**' "X"
├── "Literal string with . ( [ ?" → rg -F 'X'
├── "Just the matched token?" → rg -o 'PATTERN'
├── "Match spans multiple lines?" → rg -U 'A[\s\S]*B' (slow; last resort)
├── "Need to parse results?" → rg --json "X" | jq
└── "Too many hits?" → add -t, -g, -w, or --max-count to NARROW first
Default reflex: start with rg -l or rg -c to scope the problem, then re-run
narrowed (-t, -g, -w) before pulling full match bodies into context.
Robot Mode (machine-readable)
For tool/agent consumption, use NDJSON — one JSON object per event:
rg --json "pattern" path/
Event type values: begin (per file), match (one per matching line, carries
data.path, data.line_number, data.lines.text, data.submatches[]), end
(per-file stats), summary (totals). Parse with jq -c 'select(.type=="match")'.
scripts/rg-json.sh PATTERN [PATH] wraps this and emits path:line:text tuples —
Execute it (bash {baseDir}/scripts/rg-json.sh "TODO" src/) when you want
structured hits without writing jq inline.
Exit Codes
| Code | Meaning | Agent action |
|---|---|---|
0 |
One or more matches found | Proceed; results are on stdout |
1 |
No matches (clean, not an error) | Treat as "not found", not failure |
2 |
Real error (bad regex, unreadable path, bad flag) | Fix the command; read stderr |
Why: code 1 is the normal "absent" signal — never treat a no-match as a crash.
Only 2 is an actual failure. Use rg -q "X" && … to branch on presence cheaply.
Slow Patterns to Avoid
| Slow / wrong | Fast / right | Why |
|---|---|---|
grep -rn "x" . |
rg "x" |
parallel + gitignore-aware |
find . -name '*.js' | xargs grep x |
rg -t js "x" |
no serial find, uses type map |
rg "x" then visually filter |
rg -t go -g '!**/vendor/**' "x" |
narrow at the source |
rg -U "x" for single-line |
rg "x" |
multiline buffers whole files |
rg "a.b.c" expecting literal |
rg -F "a.b.c" |
dots are regex wildcards |
| dumping all matches into context | rg -l / -c first |
scope before you read bodies |
rg "x" $(find ...) |
rg "x" (let rg walk) |
rg's walker is the fast path |
Quality Rubric
- Did you pick the narrowest flag set first (
-l/-cbefore full bodies;-t/-gbefore whole-repo)? - Are literals searched with
-Fand words with-wso the regex engine isn't fighting you? - Did you treat exit code
1as "no match" (not an error) and2as the only real failure? - For tooling output, did you use
--jsoninstead of scraping human text?
Examples
# Find a function definition in Go, with surrounding context
rg -t go -A5 'func \(s \*Server\) Start'
# Locate every file importing a package, no bodies (fast survey)
rg -l 'github.com/boshu2/agentops'
# Count TODO/FIXME density per file, excluding tests
rg -c -g '!**/*_test.go' '\b(TODO|FIXME)\b'
# Extract all semver tags mentioned anywhere
rg -o --no-filename '\bv\d+\.\d+\.\d+\b' | sort -u
# Cross-line: a struct field block (multiline, last resort)
rg -U 'type Config struct \{[\s\S]*?\}'
# Machine-readable hits for a downstream tool
rg --json 'panic\(' cli/ | jq -c 'select(.type=="match") | {path:.data.path.text, line:.data.line_number}'
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| "No matches" but you know it's there | file is gitignored / hidden | add --no-ignore and/or --hidden (-uu for both) |
| Pattern matches too much | regex metachar in your literal | use -F, or escape . ( [ * $ |
id matches inside valid |
substring match | add -w / --word-regexp |
| Search is slow | -U on large files, or huge unfiltered tree |
drop -U; add -t/-g/--max-depth |
| Output unparseable in a script | scraping human format | switch to --json |
| Different runs reorder results | parallel walk | add --sort path for determinism |
-P complains |
ripgrep built without PCRE2 | rephrase without lookaround/backrefs |
See Also
| For… | Reference |
|---|---|
| Codex/cross-vendor parity (dual-file install) | references/codex-parity.md |
| NDJSON wrapper to run | scripts/rg-json.sh (Execute) |
| Self-check this skill's structure | scripts/validate.sh (Execute) |