fix(search): skip unreadable blobs with a warning instead of aborting - #11
Merged
Merged
Conversation
Pure move, no behavior change. main.go had grown past the 350-line target in CONTRIBUTING.md and the exit-code mapping is about to gain a case for skipped blobs, so give it its own file. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016ut4mEjEnEXdJZejxFm8gT
Since 6393c33 a single blob that cannot be read (missing or corrupt loose object, bad zlib stream, bad pack entry) aborted the whole search: workers pushed the read error onto errCh, Pipeline.Execute returned it, and AggregateChannel bailed on the first res.Error. One zero-byte object under .git/objects therefore meant exit 2 with no output. v0.1.0 silently tolerated it, and both git grep and ripgrep warn and keep going. processTask now wraps a reader.ReadObject failure in search.BlobReadError {OID, Path, Err}. The pipeline still delivers that result on resultsCh (so callers can warn) but no longer reports it on errCh, and AggregateChannel skips it instead of returning. Every other error (context cancellation, matcher failures, history walk and discovery errors) is unchanged and stays fatal. The CLI prints one line per skipped blob to stderr before any match output: grg: warning: skipping blob <oid> (<first path>): <err> Exit codes follow ripgrep: 0 when matches were found and nothing was skipped, 1 when nothing matched and nothing was skipped, 2 when any blob was skipped (matches may still have been printed). --quiet keeps its contract: a match found still exits 0, no match with a skipped blob exits 2. The README exit-code table is updated accordingly (it also claimed 128 for repository errors, which the code never returned). Tests: TestPipelineExecuteContext_ReaderError previously asserted that a read failure reached errCh, i.e. the fatal behaviour this fixes; it now asserts the soft BlobReadError path. New unit tests cover the pipeline and aggregator with a fake ObjectReader, and test/integration exercises a real repo whose loose blob is truncated to zero bytes. Fixes #10 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016ut4mEjEnEXdJZejxFm8gT
hammadmajid
added a commit
that referenced
this pull request
Sep 5, 2026
gofmt -l was failing on nine files untouched by #11, so the CONTRIBUTING format check was red on main. Whitespace and alignment changes only. The bare `grg` pattern in .gitignore also matched the cmd/grg directory, so new files there needed `git add -f`. Anchor it to the repo root. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016ut4mEjEnEXdJZejxFm8gT
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Since
6393c33a single blob that cannot be read (zero-byte or corrupt loose object, bad zlib stream, bad pack entry) aborted the entire search: workers pushed the read error ontoerrCh,Pipeline.Executereturned it, andAggregator.AggregateChannelbailed on the firstres.Error. Onemissing blobingit fsckmeant exit 2 and no output at all. v0.1.0 tolerated this by accident;git grepand ripgrep both warn on unreadable inputs and keep going.A per-blob read failure is now a soft error: the blob is skipped with a warning on stderr and the search continues, emitting results for every other blob. Context cancellation, matcher failures, history-walk and repository-discovery errors are unchanged and remain fatal.
Fixes #10
Type of Change
Key Changes
internal/search: newBlobReadError{OID, Path, Err}(Path = first occurrence path).processTaskproduces it forreader.ReadObjectfailures; workers still deliver that result onresultsCh(so callers can warn) but no longer push it ontoerrCh. All other errors keep the existing fatal path.internal/aggregator:AggregateChannelskipsBlobReadErrorresults instead of returning; any otherres.Errorstill aborts.cmd/grg: printsgrg: warning: skipping blob <oid> (<path>): <err>per skipped blob to stderr before any match output; a newskippedBlobsError(exit 2, no extra message since each blob was already reported) folds the count into the exit code. The pre-existing exit-code error types were moved intocmd/grg/errors.goin a separate pure-move commit (main.go was over the 350-line target).README.md: exit-code section updated to the ripgrep semantics below. It also previously claimed128for repository errors, which the code never returned.Exit-code semantics
-qand a match found (even with skipped blobs)-q, no match, blob skippedWarnings are printed to stderr even under
-q, matching ripgrep (which has a separate--no-messagesfor suppressing them).Verification & Testing
go test -v -count=1 ./...go test -race -count=1 ./...go vet ./...Also ran
golangci-lint run ./...(0 issues),go mod verify, andgovulncheck ./...(0 vulnerabilities in reachable code).Tests:
internal/search:TestPipelineExecuteContext_ReaderErrorpreviously asserted that a read failure reachederrCh, i.e. the exact fatal behaviour this fixes; it now asserts the softBlobReadErrorpath. NewTestPipelineSkipsUnreadableBlobuses a fakeObjectReaderwhere one OID returnsErrObjectNotFound/ErrCorruptObjectand asserts the other blobs' matches are returned, the failing OID and first path are reported, and no fatal error is returned.internal/aggregator:TestAggregator_AggregateChannel_SkipsBlobReadError; the existing test asserting a genericres.Erroris fatal is kept.test/integration/corrupt_object_test.go: creates a real repo, commits and later deletes a file (so the blob is only reachable through history, as in the issue), truncates its loose object to zero bytes, and asserts: matches from the healthy blob are printed, stderr has exactly one warning naming the OID and path, exit 2; exit 2 with no stdout when only the corrupt blob could match;-qsemantics above; and a healthy repo still exits 0/1 with empty stderr.Checklist
🤖 Generated with Claude Code
https://claude.ai/code/session_016ut4mEjEnEXdJZejxFm8gT