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.

ripgrep2code searchgitignoreagents135troubleshooting112

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-07-15
The File Is Right There in ls, and Your Agent Still Can't Open It
The agent says the file does not exist. Your terminal says it does. After three days of blaming cloud sync, the answer turned out to be that one voiced consonant mark was never a single character. Detection script and a three-layer gate included.
Agents & Manager2026-05-30
When the Antigravity Agent Says 'command not found' for node or python: Causes and Fixes
It works in your own terminal, but the Antigravity agent hits command not found. Starting from how PATH inheritance works, here are concrete fixes for nvm, pyenv, Homebrew, and WSL setups—plus how to confirm the fix actually took.
Agents & Manager2026-05-26
Why Antigravity's Browser Sub-Agent Reads SPAs as Empty Pages — and Three Wait Strategies That Stuck for Me
When you hand an SPA dashboard to Antigravity's Browser Sub-Agent, get_page_text often returns before the real content is rendered, and the agent reports an empty page. Here is how I diagnose the symptom and the three wait strategies that have stabilized my routine.
📚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
See all →