Limits
What the graph does not see
greppy builds its symbol graph from source text. Edges a program wires up at runtime are invisible to every static tool, greppy included:
- reflection;
- runtime dependency injection;
- monkeypatching;
- dynamically dispatched calls;
- macro expansion;
- code generated during the build.
greppy is built so these blind spots do not turn into wrong answers:
searchfinds code by meaning, not by graph edges. A reflection target or a generated handler stays findable when you describe what it does.who-callsreturns incomingCALLSandUSAGEedges.search-patterngives you certainty at the source level.- The grep passthrough gives you certainty at the string level.
An example from serde: every call to missing_field in serde/src/private/de.rs comes from code that serde_derive generates during the build. The call sites are tokens inside quote_spanned!. The graph sees no call there. search-pattern finds the call sites, and search finds the definition by its meaning.
$ greppy who-calls missing_field
no callers
$ greppy search-pattern 'de::missing_field'
serde_derive/src/de.rs:795 serde_derive/src/de.rs::Function::expr_is_missing
serde_derive/src/de/enum_adjacently.rs:80 serde_derive/src/de/enum_adjacently.rs::Function::deserialize
$ greppy search "treat a missing Option field as None"
serde/src/private/de.rs:45 MissingFieldDeserializer::deserialize_option — visits the None variant of an Option
…
serde/src/private/de.rs:24 missing_field — deserializes a value from a named field, returning an error if absent
…
So an empty graph result does not prove that no relation exists. Switch the navigation method before you draw a conclusion. The same applies to rename: it reports the references it could not resolve. References the graph does not see, it does not rename.
Language tiers
More than 60 languages have parser support. The graph is fully tested per language, currently for Rust, Python, Java, JavaScript, TypeScript, Go, C++, C#, Kotlin, Swift and Ruby. The other procedural languages extract the same relations without this formal guarantee. Declarative formats such as JSON, YAML, TOML and Markdown have symbols and text search, but no call graph. See Languages.
Evidence and hints
Source and graph are the authoritative evidence. A local model writes the one-sentence hints: they are navigation aids, not source, and they are missing when inference or validation fails. Read the returned code before you rely on what a function does.
Freshness
greppy never knowingly returns stale source as evidence. When the index no longer matches the working tree, the query refreshes it. While a larger refresh is running, the query returns a temporary error that tells you when to try again.
Semantic search
searchprints at most eight ranked hits. Neither--allnor--limitextends that.- While the embeddings are still building,
searchreturns a progress line and no hits. greppy never returns partial semantic results.
$ greppy search "deserialize a map" --all
serde_core/src/de/value.rs:1617 MapAccessDeserializer::new — constructs a MapAccessDeserializer from the given map
serde/src/private/de.rs:1608 MapDeserializer::new — constructs a MapDeserializer from a vector of Content values
serde/src/private/de.rs:2554 MapRefDeserializer::new — constructs a MapRefDeserializer from a slice of Content values
serde_core/src/de/value.rs:1245 MapDeserializer::new — constructs a MapDeserializer from an iterator
serde_core/src/de/value.rs:1227 MapDeserializer
serde_core/src/de/value.rs:1611 MapAccessDeserializer
serde/src/private/de.rs:1656 MapDeserializer::deserialize_any — deserializes a value by visiting its map contents
serde/src/private/de.rs:1600 MapDeserializer
Scope of one index
The graph namespace is per repository. Several packages of a workspace under one root form one shared relation space. You query several repositories one after another with --root; there is no joint query across several roots. greppy stays local: there is no central team server.
Editing
patchchanges existing files. It cannot create or delete files. You create a new file withgreppy write PATH, as a separate transaction.undorefuses when a later change touched the same span (exit 12).--verifynever downloads a checker. It uses what is installed locally and stops after a bounded timeout. It checks syntax and build; tests run as well when you setGREPPY_VERIFY_TEST_COMMAND. A failed test ends with exit 17.
$ greppy replace-text util.py "max(lo, min(x, hi))" "min(max(x, lo), hi)"
applied util.py:2 362417
│
│ 1 def clamp(x, lo, hi):
│> 2 return min(max(x, lo), hi)
$ greppy replace-text util.py "min(max(x, lo), hi)" "min(max(x, lo), hi) # clamp"
applied util.py:2 c49237
│
│ 1 def clamp(x, lo, hi):
│> 2 return min(max(x, lo), hi) # clamp
$ greppy undo 362417
util.py no longer looks the way that edit left it
Built-in agent (beta)
- It needs a local, Anthropic-compatible gateway reachable over plain HTTP (
GREPPY_ENDPOINT, defaulthttp://127.0.0.1:8317). greppy ships no chat model. - It does not support repositories with submodules yet. A repository in the middle of a merge, rebase or cherry-pick, with Git LFS or with checkout filters, stops before the model starts (exit 2).
- The sandbox confines writes, not reads or network access.
- Remote control and
agent serverun over Unix sockets. Any process running as your user can drive a running session.
See Built-in agent.
Browser tool (beta)
- The verbs can still change.
- It ships for macOS and Linux.
- A target that matches more than one node fails with exit 34, unless you pass
--first,--lastor--nth N. SubtleCryptois temporarily disabled in pages.crypto.getRandomValuesandcrypto.randomUUIDremain available.greppy web endpoint, a native Playwright connect endpoint, runs on its own release track.
See Browser tool.
Platforms
Release targets are macOS on Apple Silicon with Metal and Linux x86_64 with NVIDIA CUDA. Windows is not a release target for 0.4.1. macOS on Intel and Linux on ARM64 are not approved for release; the installer refuses them. Release builds do not compute on the CPU: --device cpu ends with exit 73. See Platforms.