Repository navigation
fix: reject a /v1/search 200 that is not a search response - #91
Merged
lukekim merged 2 commits intoOct 9, 2026
Merged
Conversation
Search decoded the body straight into SearchResponse, so a 200 carrying
{} or {"error": "..."} came back as zero results in 0ms, the same as a
real empty search. The runtime always sends results, duration_ms, and each
match's dataset, _score and matches, so a body missing one is now an error
naming the field. Score also falls back to "score", the name older
runtimes used, which previously decoded as 0.
Signed-off-by: claudespice <270518434+claudespice@users.noreply.github.com>
There was a problem hiding this comment.
🟡 Changes recommended
Case-insensitive JSON matching still permits malformed responses without the exact required field names.
1 open finding
What changed in this PR
Adds strict validation for /v1/search responses and legacy score compatibility.
Changes:
- Rejects responses missing required search fields.
- Supports the legacy
scorefield. - Adds regression tests for malformed responses.
| File | Description |
|---|---|
search.go |
Validates response fields and handles legacy scores. |
search_test.go |
Tests required fields, empty results, and legacy scores. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
encoding/json matches struct tags case-insensitively, so the presence check
accepted bodies like {"RESULTS":[],"DURATION_MS":3} that carry none of the
fields the runtime sends. Look keys up by exact name in a raw map instead.
Signed-off-by: claudespice <270518434+claudespice@users.noreply.github.com>
|
Ready to merge, blocked only on a maintainer approval. Read back just now:
Nothing here is fixable by a bot: it needs a review from a maintainer. This PR is one of a set of sibling fixes landing the same validation across the SDKs. |
lukekim
approved these changes
Oct 9, 2026
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.

What
Searchnow returns an error when a 200 response is missing a field the runtime always sends. Before, it returned an empty result.Why
Searchdecoded the body straight intoSearchResponse, so every absent field became Go's zero value. A 200 carrying{}, or{"error": "upstream timeout"}from a proxy answering for the runtime, came back as&{Results:[] DurationMs:0}with a nil error. A caller can't tell that apart from a search that matched nothing, and neither can its telemetry.The runtime always serializes these fields.
resultsandduration_msare incrates/runtime/src/http/v1/search.rs, and each match'sdataset,_scoreandmatchesare incrates/runtime-search/src/types.rs(spiceai trunk2c0c7b0723).primary_key,dataandmetadataare omitted when empty, so they stay optional. The error names the missing field, for examplenot a search response: result 0 is missing "_score".Scorealso falls back toscore, the name older runtimes used. The runtime's own search test client accepts the same alias. Before this change ascorefield decoded as0, which is silently wrong.spice-rs has the same fix in spiceai/spice-rs#105. spicepy gets it in a parallel PR.
Verification
The new tests fail on trunk
ff450ab.go test -run TestSearch -v ., with the tests applied and the fix not applied:With the fix:
ok github.com/spiceai/gospice/v9for-run TestSearch.Gate on this branch:
go build ./...okgo vet ./...okgolangci-lint run ./...printed0 issues.go test -count=1 ./...had 5 failures:TestADBCLocalParameterizedQuery,TestADBCLocalBasicQuery,TestAsyncQueryLocal,TestLocalRuntimeDatasetRefresh,TestLocalRuntime. All 5 need a local runtime and fail on trunk here too.Review gate
Attests the review-fix commit 70f696e (fix-failing-prs), not the original change.
codex— jobreview-muyo83j6-skde52on the first draft: needs-attention, one high finding (a cased duplicate key such asRESULTSbesideresultsmade the struct decoder and the exact-key check read different arrays → index-out-of-range panic). Reproduced (panic: runtime error: index out of range [0] with length 0), fixed by rejecting case variants plus a length guard, regression tests added. Re-runreview-muyo9v4x-m8cpld: approve, no findingsgo test -run TestSearch ./...before the fix: 4 uppercase-key subtestsexpected an error, got &{Results:[] DurationMs:3}; after:ok.golangci-lint run ./...: 0 issues