Hardcover search never returns any results - #71
Open
JordanFromIT wants to merge 1 commit into
Open
Conversation
Hardcover rejects a batched request body — a JSON array of operations — with
400 invalid_query ("Unexpected end of document"). Search sent all three of its
lookups that way, and the author-books follow-up sent its id chunks the same
way, so Hardcover search fails for every user regardless of their token.
Send each operation as its own request instead. The query text, variables and
response handling are unchanged; only the transport shape differs.
Contributor
|
The errors you were seeing were part of hardcover switching things on their end. Are you still seeing issues now they've finished the migration? You shouldn't be, let me know |
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
Hardcover search never returns any results. Chaptarr packs its three lookups — Author, Book and Series — into a single HTTP request as a JSON array of GraphQL operations, and Hardcover rejects an array body outright with
400 invalid_query. The same thing happens on the author-books follow-up, which batches its id chunks the same way. Because the failure is at the transport layer rather than the query layer, it happens for every user with every valid token, and the surfaced error points somewhere else entirely — mine said "Hardcover's API is currently unavailable. Please try again later." while Hardcover was perfectly healthy. This sends each operation as its own request. The queries, the variables and all of the response handling are unchanged.Reproduced against the live API with a valid token, changing nothing but the shape of the body:
[{query,variables}, {…}, {…}]— what Chaptarr sends400{"error":"invalid_query","error_description":"Unexpected end of document"}{query,variables}— one operation200, 817 hitsThat is the whole bug. The GraphQL itself is fine — Chaptarr's exact search query, and its exact token-validation query, both return
200when sent on their own.Technical detail
Two call sites build an array body:
ExecuteSearch—SearchTargets.Select(...).ToList()serialised straight into the request, so all three search types travel together.authorIdChunks.Select(...).ToList(), one operation per chunk of five author ids.Both now loop and send one operation per request. To keep that from duplicating request setup a third time, the builder is extracted into
BuildGraphQlRequest(jsonContent), which is the same construction as before including addingAuthorizationafterBuild().Response handling follows the body shape.
TryExtractSearchPayloads(which required a top-level array whose length matchedSearchTargets.Length) is replaced by aTryExtractSearchPayload(queryType, responseContent, …)overload that parses one response and delegates to the existing per-operation logic. That per-operation method — including its GraphQLerrorshandling and theUNAUTHENTICATEDdetection — is untouched. The author-books loop likewise now readsdatafrom each response directly instead of indexing intoroot[chunkIndex].ENRICHMENT_QUERYalready sent a single operation and is not affected.Deliberate choices worth flagging for review:
should_send_one_search_root_per_http_batched_operationexplicitly assertedValueKind == Arraywith three operations, andshould_chunk_ten_author_book_roots_into_two_http_batched_operationsasserted a two-element array body. Both are rewritten to assert the per-request shape and renamed accordingly. I have kept every assertion that was about content and changed only the ones about packaging, so the coverage those tests provided is not lost.Known gap, deliberately not addressed:
ValidateHardcoverTokenmaps any unrecognised status to "Hardcover's API is currently unavailable", which is what sent me looking at Hardcover's status page instead of at this. Improving that message is a separate, unrelated change and I did not want to bundle it. Happy to follow up.Database Migration
NO. No schema changes, no new columns, no stored data of any kind. The change affects only how an outbound HTTP request is framed; nothing is persisted, so there is nothing to migrate or back-fill and reverting the commit fully reverts the behaviour.
How was this tested?
Built and tested on Linux, .NET 10, in the
mcr.microsoft.com/dotnet/sdk:10.0image, using the same stepsbuild.ymlruns:git grepmerge-conflict markerspackage.jsonparsesversion_guard.py syncVersion sync OK for working tree: 0.9.929version_guard.py monotonic --compare-ref origin/developVersion monotonic OK: 0.9.929version_guard.py commit-hygiene --compare-ref origin/developOK for range origin/develop..HEADdotnet build src/Chaptarr.NoTests.sln -c Releasedotnet test src/Chaptarr.Core.Test -c ReleaseTest counts. Clean
origin/developat5713d83: 2838 passed. This branch: 2839 passed, 0 failing. Both measured from a hard-reset,git cleaned tree with_output/_testsremoved.HardcoverSearchClientFixtureitself goes from 42 to 43 tests.No frontend files are touched — 2 files changed, both
.cs— so the yarn steps outsidebuild.ymlare not applicable.New test:
should_send_each_search_type_as_its_own_requestasserts three separate requests, each with a top-level JSON object body, carrying the three distinctquery_typevalues and the sameq/limit/pagevariables as before.Confirmed the tests catch the bug by restoring only
HardcoverSearchClient.csto itsdevelopversion while keeping the new tests, then re-running the fixture:Restoring the fix returns
Passed! - Failed: 0, Passed: 43.End to end, Docker on Linux, against a real instance with a valid Hardcover token. Before this change every Hardcover search failed with
400 BadRequest, logged asHardcover search failed. After:GET /api/v1/search?term=Harry Potter and the Philosopher's Stone503,Hardcover search failed200, 21 results — correct book, author and series all presentGET /api/v1/series/lookup?foreignSeriesId=…400200—Harry Potter,workCount 7,primaryWorkCount 7, books returned in positions 1–7Both search and the series lookup path exercise the batching code, and both were dead before the change.
Screenshots (UI changes only)
None — no UI changes.