◉ANTIGRAVITY LABJP
Articles/Agents & Manager
◈ Agents & Manager/2026-08-28Intermediate

Git Tracks the File, but Your Agent Can't See It. Here's Where It Drops Out

An agent's code search returned two files. git grep returned six. I traced the three paths that quietly drop tracked files out of search, measured them on a synthetic repo and a production one, and worked out how to spot the gap in a single line.

ripgrep3code searchgitignoreagents144troubleshooting113

I asked an agent to list every place a constant was used. It came back with two files. Running git grep by hand turned up six.

The agent had not cut corners. The search defaults simply never put the other four files in scope.

Antigravity CLI 1.1.21, released on August 26, now ships a ripgrep binary for agent code search. Not depending on whatever happens to be installed on the machine is a real improvement. What bundling does not change is the set of things ripgrep declines to look at by default.

A file the search never opens is, from the agent's point of view, a file that does not exist. It will happily tell you the list is complete. I find that silent gap more costly than a slow search, and it is much harder to notice.

Reproducing a six-into-two search

Here is the smallest repository I could build that shows the behaviour. The same constant, NEEDLE_TOKEN, goes into six files. Three of them land somewhere .gitignore covers, and one goes into a hidden directory.

mkdir -p src/generated src/legacy .config build
echo 'const NEEDLE_TOKEN = 1;' > src/app.ts
echo 'const NEEDLE_TOKEN = 2;' > src/generated/api-client.ts
echo 'const NEEDLE_TOKEN = 3;' > src/legacy/old.ts
echo 'const NEEDLE_TOKEN = 4;' > .config/settings.ts
echo 'const NEEDLE_TOKEN = 5;' > build/bundle.js
printf 'NEEDLE_TOKEN\x00binary\n'  > src/blob.bin
 
printf 'build/\n*.bin\nsrc/generated/\n' > .gitignore
 
# Ignored by pattern, tracked anyway. A very common arrangement.
git add -f src/generated/api-client.ts
git add -A -f && git commit -m init

The git add -f line is the interesting part. Keeping generated code out of .gitignore's reach while force-tracking one specific artifact is a normal thing to do. Working solo as an indie developer, I carried a generated API client in exactly that shape for a long stretch, without once thinking about what it did to search.

Three searches over the same commit:

git grep -l NEEDLE_TOKEN | wc -l    # 6
rg -l NEEDLE_TOKEN . | wc -l        # 2
rg -l -uuu NEEDLE_TOKEN . | wc -l   # 6

git grep returns all six tracked files. ripgrep's defaults return src/app.ts and src/legacy/old.ts. Four files that Git is version-controlling sit outside the search.

These numbers come from ripgrep 13.0.0 on Linux. The bundled binary will not necessarily report the same version as your system rg, but the default filters described below behave the same way across that lineage.

Three separate exits, not one

The four missing files did not disappear for the same reason.

FileWhy it dropped outFlag that restores it
build/bundle.jsbuild/ in .gitignore--no-ignore
src/generated/api-client.tssrc/generated/ in .gitignore, tracked or not--no-ignore
.config/settings.tsHidden directory--hidden
src/blob.binContains a NUL byte, treated as binary--binary or --text

Adding the flags one at a time walks the count from two up to six:

rg -l NEEDLE_TOKEN .             # 2
rg -l --no-ignore NEEDLE_TOKEN . # 4  (+ generated, + build)
rg -l -uu NEEDLE_TOKEN .         # 5  (+ .config)
rg -l -uuu NEEDLE_TOKEN .        # 6  (+ blob.bin)

The second line is the one that surprised me. ripgrep never asks whether a file is tracked. It only asks whether the path matches an ignore pattern. A file you force-added is a first-class citizen to Git and an ignored path to ripgrep, and nothing in either tool's output points out that the two disagree.

Binary detection reports itself inconsistently, which makes it worse. During a directory walk, a binary file is dropped in silence. Name the file directly and you get an explanation:

rg NEEDLE_TOKEN src/blob.bin
# binary file matches (found "\0" byte around offset 12)

You never see that line for files skipped during traversal. "No matches" and "never opened" produce identical output.

The scope can differ from machine to machine

This is the part that is hardest to catch, because the cause is not in the repository at all. .gitignore is not the only ignore source ripgrep honours.

echo 'src/legacy/' >> .git/info/exclude
rg -l NEEDLE_TOKEN .    # now only src/app.ts

.git/info/exclude never gets committed. It exists only in your clone. The global core.excludesFile setting behaves the same way and applies across every repository on the machine.

So two people on the same commit can hand their agents different search scopes. Solo development is not exempt either: if a desktop and a laptop have drifted apart, the same prompt produces different answers on each. When someone reports that a symbol is findable on their machine but not yours, this is now on the list of things to check.

--debug names the rule that did it

There is no need to guess. Passing --debug prints one line per exclusion decision.

rg --debug --files . 2>&1 | grep "ignore::walk"

The output looks like this:

ignoring ./build: Ignore(IgnoreMatch(Gitignore(Glob { from: Some("./.gitignore"),
  original: "build/", actual: "**/build", is_whitelist: false, is_only_dir: true })))
ignoring ./.config: Ignore(IgnoreMatch(Hidden))
ignoring ./src/blob.bin: Ignore(IgnoreMatch(Gitignore(Glob { from: Some("./.gitignore"),
  original: "*.bin", actual: "**/*.bin", is_whitelist: false, is_only_dir: false })))

from tells you which file supplied the pattern, and hidden paths are tagged separately as Hidden. When .git/info/exclude is responsible, its path shows up in from, which is how you catch a rule that was never shared with anyone.

For a summary rather than a list, pipe the tail into a counter:

rg --debug --files . 2>&1 | grep "ignore::walk" \
  | sed -E 's/.*IgnoreMatch\(([A-Za-z]+).*/\1/' | sort | uniq -c | sort -rn

Widening the scope, temporarily and permanently

For a one-off investigation, -uu or -uuu is enough. Asking an agent to remember the flags is not a plan, though. It holds for the session you wrote it in and quietly lapses in the next one.

For files you want back permanently, put a negation pattern in an .ignore file. ripgrep gives .ignore precedence over .gitignore.

printf '!src/generated/\n' > .ignore
rg -l NEEDLE_TOKEN .    # 3 now (app / legacy / generated)

.ignore can be committed, so the decision travels with the repository. You can hand back exactly the generated code you want an agent to read and nothing else.

One caveat I ran into: writing !*.bin into .ignore does not bring the binary back. The two exclusions live in different layers, and .ignore only reaches the ignore-rules layer. Binary content needs --binary. In practice there are very few reasons to want an agent reading binaries, so leaving that one shut seems right to me.

If what you actually want is "everything Git tracks, no more and no less", run git grep alongside. It keys off tracking status directly, so how .gitignore happens to be written stops mattering.

One line that shows you the gap

Whether the two scopes have drifted apart is a single command:

comm -23 <(git ls-files -z | tr '\0' '\n' | sort) \
         <(rg --files | sed 's|^\./||' | sort)

Anything Git tracks that ripgrep does not walk gets printed. An empty result means the scopes agree.

The -z matters. The repository behind this site contains src/app/[locale]/HomeClient.tsx, and plain git ls-files emits that path quoted and escaped, which manufactures one phantom difference. Setting core.quotePath=false does not fix it either, which I confirmed rather than assumed. Switching the separator to NUL makes the two lists directly comparable.

Here is what the command reports against the repository that serves this site:

MeasurementValue
Files tracked by Git2,264
Files returned by rg --files2,252
Difference12 (.gitignore, .npmrc, ten per-category .gitkeep files)
Exclusion reasonsAll Hidden

I measured this with node_modules absent, so nothing dropped out through .gitignore here. The only losses were hidden files, none of which contain searchable code. No harm done in this case. A repository that tracks generated code will behave like the synthetic example instead.

I also checked what the extra flags cost. Across 2,252 files: 20 ms for the defaults, 17 ms with -uu, 20 ms with -uuu, and 20 ms for git grep. At this size the differences are noise. Scope is not a decision you need to trade against speed yet.

A slow search announces itself. A search that skipped half the codebase does not. That asymmetry is the reason I now keep the comm line in the same checklist as my build and test commands. If you want it running from inside the editor, I covered that loop in building a verification loop with Antigravity 2.10.0's embedded terminal.

The related failure mode, where the search engine itself swaps out underneath you, is measured separately in what happens when /codesearch cannot run ripgrep. If your exclusion patterns are the suspect rather than the defaults, four places to check when .antigravityignore has no effect walks through that.

For the wider question of how much context to hand an agent and what it costs, how to pass a read-only .git to an agent documents cutting the same history transfer from 38 seconds to 0.4.

Start by running that comm line against your own repository. The number of lines it prints tells you how much of your code your agent is currently able to see.

Share

Thank You for Reading

Antigravity Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $15 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

◈ Agents & Manager2026-09-08
Before I accept what /boost says it verified, I count what its throwaway worktree never saw
Antigravity's /boost runs its tests inside an ephemeral isolated worktree. If your build reaches for files git never tracked, that verification passed on a different tree than yours. Here is how I measure the gap first.
◈ Agents & Manager2026-09-06
With scheduled agents, I now look for runs that never happened before I look for failures
My run ledger showed a 100% success rate while the evening slot had not fired for two weeks. Here is the reconciliation I now run against an expected-fire table, with measured notes on cron expansion, exit codes, and how the ledger itself gets written.
◈ Agents & Manager2026-09-04
Snapshot Your Agents' Effective Scope So You Notice the Day a Default Changes
Antigravity 1.1.25 made Markdown-defined custom agents inherit the surrounding skills, rules, and subagents by default. Here is the snapshot-and-diff routine I now use to check whether an agent I meant to keep narrow quietly got wider after an update.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links