Surface HTTP-level GraphQL failures by status, not as parse errors - #72
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Adversarial-review pass (independent subagent, live API probing included) applied in the second commit:
Review also verified live against api.runpod.io/graphql: real 400s do carry GraphQL 🤖 Generated with Claude Code |
justinwlin
left a comment
There was a problem hiding this comment.
Approving with one fixup commit pushed (4071653), addressing the two review nits:
- 401 hint scoped to credentialed call sites. The re-auth hint moved out of the HttpError constructor into the callers via the existing
hintparam — REST client, authed GraphQL, and the SSE log reader still send it; the credential-free public GraphQL path no longer advises rotating an API key it never sent (consistent withobserveUnauthorized's existing rationale). - Non-OK-with-GraphQL-errors now throws HttpError (messages as body) instead of a plain Error, so
.status/.namestay machine-readable. Message shape:Runpod GraphQL Error: 400 - <message>. - 4 new goldens pin hint presence/absence per path (REST/authed-GraphQL/SSE present, public-GraphQL absent) — previously the only 401-hint test asserted it on the public seam.
512 pass / 0 fail, type-check + prettier clean on touched files.
graphqlRequest called response.json() unconditionally, so a 429/5xx returning an HTML body surfaced as an opaque "Unexpected token '<'" parse error. Non-OK responses now throw the same HttpError as the REST client — status + body, RateLimit wait hint on 429 (reusing the #64 machinery) — while a non-OK body that still carries a GraphQL errors array keeps the readable GraphQL message with the status attached. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Array.isArray guard on the parsed errors field: proxies/WAFs emit
{"errors":"..."} where a non-empty string passes a .length check and
.map() then throws a TypeError worse than the parse error this change
eliminates; item messages are stringified defensively too
- 401/429 with a GraphQL-shaped body now fall through to HttpError so
the re-auth and RateLimit hints are never suppressed
- HttpError caps embedded bodies at 2KB in the message (.body keeps the
full text) so a large WAF/proxy error page can't flood agent context
- Test harness steps can carry response headers; goldens added for the
RateLimit-header hint content, non-array errors, 401 precedence, and
body truncation
- Changeset no longer names get-capacity (lives on unmerged #71)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…rrors throws HttpError The re-auth hint was baked into the HttpError constructor, so the credential-free public GraphQL path advised rotating an API key it never sent. The hint now travels through the existing hint param from each call site that actually carried a credential (REST client, authed GraphQL, SSE log reader); the public path's 401 stays hint-free. The non-OK-with-GraphQL-errors branch now throws HttpError (extracted messages as the body) instead of a plain Error, keeping .status machine-readable. New goldens: REST 401 hint present, authed-GraphQL 401 hint present, SSE 401 hint present, public-GraphQL 401 hint absent.
4071653 to
55c0250
Compare
Summary
Completes for the GraphQL path what #64 did for the REST client.
graphqlRequestcalledresponse.json()unconditionally with noresponse.okcheck, so an HTTP-level failure — a 429 from the public rate limiter, a 5xx, a WAF page — surfaced as an opaqueUnexpected token '<'parse error. That path serves every GraphQL-backed tool:list-gpu-types,list-data-centers,get-capacity(#71),list-hub-repos,list-public-endpoints,deploy-hub-repo,set-endpoint-gpus— and the public host is the credential-free (most rate-limited) one.Non-OK responses now throw the same
HttpErroras the REST client: status and body named, with theRateLimit-header wait hint on 429 (reusing #64'srateLimitHintunchanged). One refinement: a non-OK body that still carries a GraphQLerrorsarray (servers commonly 400 on malformed queries) keeps the readable GraphQL message with the status attached —GraphQL Error (HTTP 400): <message>— instead of dumping raw JSON.Found during two-agent adversarial review of #71 (probe mode fans out up to 12 concurrent public GraphQL calls, multiplying exposure to exactly this failure). Kept as its own PR since it changes error behavior for every GraphQL tool. Merge-order note: independent of #71 — whichever lands second just works; with both merged, a rate-limited probe lands as a readable per-version
probeErrorsentry instead of an opaque one.Test plan
HttpErrornaming the status with back-off guidance (not a parse error); 400 with GraphQL errors body → readable message + status; 200 with errors array → message unchanged (golden).🤖 Generated with Claude Code