Skip to content

▚▚ fix(agent): surface ripgrep errors from grep instead of an empty result - #103

Open
breken-ai wants to merge 1 commit into
magnitudedev:mainfrom
breken-ai:fix/grep-rg-error-exit
Open

breken-ai wants to merge 1 commit into
magnitudedev:mainfrom
breken-ai:fix/grep-rg-error-exit

Conversation

@breken-ai

Copy link
Copy Markdown

The agent's grep tool answers "no matches" when ripgrep actually failed. Give it a pattern rg cannot compile, such as (, and the tool returns []; the model concludes the code it was looking for does not exist and moves on. rg's own explanation (regex parse error ... unclosed group) is thrown away.

Root cause

rgSearch in packages/agent/src/services/fs.ts spawns rg with --json, parses match lines from stdout, and returns whatever it collected. It never inspects the exit status. ripgrep exits 1 for "no matches" and 2 for an error, so an invalid regex (exit 2, nothing on stdout) looks exactly like an empty search. stderr is piped but never read, so the reason is lost as well. The grep tool then collapses every FsError into a bare Search failed for <pattern>, so even when a cause exists it is not shown.

Fix

  • packages/agent/src/services/fs.ts: drain stderr concurrently (new Response(proc.stderr).text()), read rg's exit code from proc.exited, and when it is 2 with no matches collected, fail with rg's stderr message. Exit 2 with matches (rg hit unreadable files but still matched) keeps returning the matches, so nothing that used to work is dropped.
  • packages/agent/src/tools/fs.ts: include the underlying cause in the grep tool's Search failed for <pattern>: <cause> error so the model can fix its pattern.
  • .changeset/grep-invalid-regex-error.md: patch changeset for @magnitudedev/cli.

Tests

packages/agent/src/services/fs.test.ts runs the real vendored rg binary through FsLive.search (with resolveRgPath mocked to packages/ripgrep/bin/rg, since the embedded payload only resolves in a compiled build). Without the fix, search('(') succeeds with [] and the test fails on Exit.isFailure; with the fix it fails with a message containing regex parse error. Two companion cases pin the unchanged behaviour: a valid pattern returns [{ file: 'a.txt', match: '1|hello world' }], and a non-matching pattern returns [] without failing.

Testing conducted: regression test red on HEAD, green with the fix, red again with the exit-code guard disabled; full bun test in packages/agent shows the same 28 pre-existing failures with and without this change; tsc --noEmit in packages/agent and packages/acn report no errors.

Not changed

  • The 5 s search timeout and the 50-match limit are untouched.
  • rg exit 2 with partial matches still returns the matches rather than failing; only the "error and nothing found" case is now an error.

▚▚ Shipped by breken — self-healing software. This one's on us. breken.ai

`rgSearch` only parsed ripgrep's stdout and ignored its exit status, so
any run that ended in an error (exit 2) with nothing collected — most
commonly an invalid regex such as `(` — resolved to `[]`. The grep tool
then told the model there were no matches, which is false: the search
never ran. stderr was piped but never read, so the reason was lost too.

Now stderr is drained concurrently and, when rg exits 2 with no matches
collected, the search fails with rg's own message. Exit 2 with matches
(partial read errors) still returns the matches. The grep tool includes
the underlying cause in its `Search failed` error so the model can
correct the pattern.

Claude-Session: https://claude.ai/code/session_0134ujLF81GyXsCByibLcYsz
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant