ANTIGRAVITY LABJP
Articles/Antigravity Basics
Antigravity Basics/2026-08-18Intermediate

How Much Slower Search Gets When /codesearch Cannot Run ripgrep

Antigravity CLI 1.1.13 moved the bundled ripgrep binary, and /codesearch quietly falls back to local search when it cannot run. I measured the cost of that fallback on a real repository and wrote down how to tell which path your machine is on.

Antigravity CLI26codesearch2ripgrepPerformance6Environment Setup

Buried in the Antigravity CLI 1.1.13 notes is a line saying that /codesearch falls back to local search when the bundled ripgrep binary cannot be executed.

That line stopped me.

The fallback is silent. No error, no warning, and results still come back. Which means you can keep using a slower search path indefinitely without ever learning that you switched to it.

I did not want to keep using it blind, so I measured the search paths themselves on a repository I actually run, and worked out how to tell which side of the fallback a machine is on.

One caveat up front: what I measured is not the CLI's internals. It is the search engines the fallback moves between.

As an indie developer moving between app and site repositories all day, a few hundred milliseconds per search adds up dozens of times over. It is the kind of difference worth making visible.

What changed in 1.1.13

Four search-related changes are documented in the 1.1.13 release (August 14, 2026).

ChangeDetail
Extraction pathThe bundled ripgrep binary now unpacks into the user cache directory instead of /tmp
Integrity checkThe extracted binary is verified with SHA-256
ConcurrencyAn atomic rename avoids collisions between simultaneous runs
/codesearch resilienceFalls back to local search when ripgrep cannot be executed

If you have run into this in practice, the move away from /tmp makes immediate sense. Plenty of machines mount /tmp with noexec, which means a binary can be written there and still refuse to run. Shared boxes and hardened containers do this routinely.

The reasons a binary fails to execute usually fall into three buckets:

  1. /tmp or the cache directory is mounted noexec
  2. An endpoint security product blocks unsigned binaries from executing
  3. The cache directory is not writable

The 1.1.13 change relaxes the first one. The other two survive it, because they live outside the CLI.

The exit code tells you which path you are on

"Cannot execute" shows up as shell exit code 126. You can reproduce it locally in four lines:

cp "$(command -v rg)" "$HOME/rgtest"
chmod -x "$HOME/rgtest"
"$HOME/rgtest" --version
echo "exit=$?"

On my Linux box:

bash: /home/user/rgtest: Permission denied
exit=126

126 means the file was found but could not be run. Both noexec mounts and security-product blocks land here, and it is distinct from 127, which means the file was not found at all.

One trap worth naming. Do not pipe the command you are testing into head or tail. $? then reports the exit status of the last stage of the pipe, and a 126 turns into a 0. I fell for this once during these measurements and briefly concluded the binary was running fine.

Three things are worth checking on a suspect machine:

What to checkCommandWhat you want
Mount options for /tmpfindmnt -no OPTIONS /tmpNo noexec
Mount options for the cache directoryfindmnt -no OPTIONS "${XDG_CACHE_HOME:-$HOME/.cache}"Same, and this is the one that matters since 1.1.13
Whether the binary runsThe chmod test aboveExit code other than 126

The exact extraction path varies by environment. To find it:

find "${XDG_CACHE_HOME:-$HOME/.cache}" -type f -name 'rg*' 2>/dev/null

Measuring three search paths on the same repository

The test subject is a Next.js site repository I run in production: 2,225 files and 49 MB excluding .git, mostly MDX and plain text.

Three paths were compared:

  • ripgrep 13.0.0 (rg -n)
  • GNU grep (grep -rn --exclude-dir=.git)
  • A deliberately plain Python implementation, standing in for an unoptimized local search

The third one is not the actual fallback implementation. It is a reference point for "a search with no special optimizations":

import os, re, sys, time
 
root, pat = sys.argv[1], sys.argv[2]
rx = re.compile(pat)
hits = 0
t0 = time.perf_counter()
 
for dirpath, dirnames, filenames in os.walk(root):
    dirnames[:] = [d for d in dirnames if d != ".git"]
    for fn in filenames:
        path = os.path.join(dirpath, fn)
        try:
            with open(path, "r", encoding="utf-8", errors="ignore") as f:
                for line in f:
                    if rx.search(line):
                        hits += 1
        except (OSError, UnicodeError):
            continue
 
print(f"{hits} hits {(time.perf_counter() - t0) * 1000:.0f} ms")

It walks the tree and runs a regex against each line, excluding only .git, to stay close to the minimum any fallback would plausibly implement.

Each query ran five times; the numbers below are medians with a warm page cache.

PathLiteral searchRegex search
ripgrep18 ms19 ms
grep -rn32 ms39 ms
Plain Python scan277 ms281 ms

At this size even the slowest path finishes in a third of a second. It feels like a slight pause, not like something worth investigating.

Scale is where it starts to matter. I duplicated the content six times into a 200 MB, 12,654-file corpus and measured again. Since it is duplicated content, it lacks the variety of a real monorepo — read these as a signal about how size behaves, not as a benchmark of your codebase.

Path49 MB / 2,225 files200 MB / 12,654 filesGrowth
ripgrep18 ms56 ms3.1x
grep -rn32 ms141 ms4.4x
Plain Python scan277 ms1,510 ms5.5x

Roughly four times the input, and ripgrep grew 3.1x while the plain scan grew 5.5x. The gap between them widened from 15x to 27x.

So the difference you did not notice on a small repository is exactly the difference you will notice on a project carrying node_modules — and even more so if your agent fires several searches per turn.

The change in coverage matters more than the change in speed

Something else showed up mid-measurement that I find more consequential than the timings: the two paths do not walk the same set of files.

rg --files . | wc -l    # 2213
find . -path ./.git -prune -o -type f -print | wc -l    # 2225

Twelve files apart. Listing them gave .gitignore, .npmrc, and a .gitkeep in each content category. ripgrep skips hidden files by default.

In this repository none of those twelve contained the search term, so all three paths reported the same 9,714 matching lines. No divergence at all.

But that was luck of the draw. ripgrep also honors .gitignore by default, and a project with node_modules or build output will produce very different result volumes depending on whether the fallback applies the same exclusions. There is no guarantee that it does.

A speed difference costs you a wait. A coverage difference changes the context your agent reads. If stale code inside a build directory enters the result set, the agent treats it as current source. That is the part I would worry about first.

What to try once you know the binary will not run

In order:

  1. Confirm the exit code really is 126. Changing settings before you have pinned this down means backtracking later
  2. Check the mount options for both /tmp and the cache directory. Since 1.1.13 the extraction happens on the cache side, so fixing /tmp alone changes nothing
  3. When you file a security exception, mention that the extracted binary is verified with SHA-256. It gives whoever reviews the request something concrete to approve
  4. On machines that will never allow it, running on the fallback is a legitimate choice. From these measurements that is roughly 2 to 2.5x for a grep-class path and 15 to 27x for a plain scan. The former is tolerable in a lot of situations
  5. Revisit what your queries actually match. Query design moves results more than path speed does

On that last point, I have written separately about measuring the regex defaults /codesearch applies. The harder it is to fix your search path, the more it pays to get everything you need out of a single query — see Treating Code Search as a Contract: Measuring the /codesearch Regex Default if that is where you are.

If your machine also refuses to sign in, Running Antigravity CLI 1.1.13 on a Machine You Cannot Sign In From covers the other half of the constrained-environment story. On locked-down hardware these two tend to arrive together.

Start with the chmod -x reproduction on your own machine, just once. Once you know whether you get a 126, the next thing to touch picks itself.

Thank you for reading through the measurements with me.

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 $10 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

Antigravity2026-08-17
Deleting Duplicate Rows Will Not Shrink Your Antigravity CLI Conversation Database
A schema-agnostic way to audit conversation database growth, plus measurements showing why deletion reclaims nothing and why freelist_count is the wrong number to trust when you estimate how much you can get back.
Antigravity2026-08-16
Running Antigravity CLI 1.1.13 on a Machine You Cannot Sign In From
I put agy on a box with no browser and the sign-in screen stopped me cold. Here is how direct GEMINI_API_KEY auth from CLI 1.1.13 works, how to confirm it took effect, and why you should not hand it the key your app already uses.
Antigravity2026-08-10
The Line I Thought Matched Nothing Was Approving Everything: Auditing Empty Allowlist Entries
Allowlist entries that decompose to zero command words matched every command and silently auto-approved it. Here is how I scanned my own config, separated the hole the fix closed from the one it did not, and rewrote matching to a prefix-token comparison.
📚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 →