abhigyanpatwari/GitNexus · error · RetryableEmbeddingBodyError
Embedding endpoint returned an unparseable response (${safeU
Error message
Embedding endpoint returned an unparseable response (${safeUrl(url)}, batch ${batchIndex}) What it means
A module-private RetryableEmbeddingBodyError thrown inside the retried fetch callback when the endpoint returns 2xx but the response body cannot be parsed as JSON (e.g. an HTML error page, a captive-portal page, or a truncated stream), provided the parse failure is not itself a terminal network abort (isTerminalNetworkError re-raises untouched). resilientFetch classifies it as retryable-network so the bad body gets the same exponential backoff and circuit-breaker accounting as a 503. It never escapes httpEmbedBatch directly: after retries are exhausted it is converted into the terminal HttpEmbeddingError at error 127.
Source
Thrown at gitnexus/src/core/embeddings/http-client.ts:460
// and the same breaker accounting as any other endpoint fault (#2790).
let payload: { data: EmbeddingItem[] };
try {
payload = (await attemptResp.json()) as { data: EmbeddingItem[] };
} catch (err) {
// Not every `.json()` rejection is a parse error: the per-attempt
// signal (`AbortSignal.any([caller, AbortSignal.timeout(...)])`) is
// wired to the body stream, so a stalled body rejects with the abort
// reason. Re-raise those untouched — `isTerminalNetworkError` is
// `resilientFetch`'s own predicate, so this test agrees with
// `classifyOutcome` by construction. Wrapping one would flip its
// verdict from `terminal-network` (returned without retry AND
// without touching the breaker, via `recordNeutral()`) to
// `retryable-network` (retried, then `breaker.recordFailure()`): the
// same timeout would take 3 attempts instead of 1, count toward the
// process-global `embeddings-http` breaker, and reach the operator as
// "unparseable response" so they never reach for the timeout knob.
if (isTerminalNetworkError(err)) throw err;
throw new RetryableEmbeddingBodyError(unparseableMessage(), { cause: err });
}
if (!Array.isArray(payload?.data) || !payload.data.every(isEmbeddingItem)) {
throw new RetryableEmbeddingBodyError(unexpectedShapeMessage());
}
// Cardinality belongs *inside* the retry loop. `every(isEmbeddingItem)`
// is vacuously true for `[]` and true for any array shorter than the
// request, so a 200 carrying `{"data": []}` — or half the vectors —
// used to be classified `success`, call `breaker.recordSuccess()`
// (erasing the outage signal), and only then fail terminally after a
// single attempt. A short body is a truncated body: same backoff, same
// breaker accounting as any other endpoint fault (#2790).
if (payload.data.length !== batch.length) {
throw new RetryableEmbeddingBodyError(
countMismatchMessage(payload.data.length, batch.length, safeUrl(url), batchIndex),
);
}
parsed = payload.data;
return attemptResp;View on GitHub (pinned to d540b00184)
Solutions
- Verify the endpoint with curl: `curl -sS -o /dev/null -w '%{content_type}\n' -X POST "$GITNEXUS_EMBEDDING_URL/embeddings" -H "Authorization: Bearer $GITNEXUS_EMBEDDING_API_KEY" -H 'Content-Type: application/json' -d '{"model":"'$GITNEXUS_EMBEDDING_MODEL'","input":"test"}'` — it must be application/json.
- Correct GITNEXUS_EMBEDDING_URL to the OpenAI-compatible base (no trailing /embeddings; the client appends it), e.g. https://api.openai.com/v1.
- If the truncation is intermittent, raise GITNEXUS_EMBEDDING_HTTP_TIMEOUT_MS and reduce HTTP_BATCH_SIZE pressure by embedding smaller corpora; the retry/backoff should recover transient cases.
- Check ingress/proxy buffering and gzip settings on the embedding service.
Example fix
// before: URL points at the marketing site export GITNEXUS_EMBEDDING_URL=https://provider.example.com // after: URL points at the OpenAI-compatible API base export GITNEXUS_EMBEDDING_URL=https://provider.example.com/v1
Defensive patterns
Strategy: retry
Validate before calling
// Probe the endpoint once before the indexing run:
const probe = await fetch(`${process.env.GITNEXUS_EMBEDDING_URL}/embeddings`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${process.env.GITNEXUS_EMBEDDING_API_KEY}`,
},
body: JSON.stringify({ model: process.env.GITNEXUS_EMBEDDING_MODEL, input: 'probe' }),
});
const ct = probe.headers.get('content-type') ?? '';
if (!ct.includes('application/json')) {
throw new Error(`Endpoint is not JSON (content-type: ${ct}); check GITNEXUS_EMBEDDING_URL`);
} Type guard
// RetryableEmbeddingBodyError is module-private; catch its terminal form instead.
import { isHttpEmbeddingError } from 'gitnexus';
const isUnparseableTerminal = (e: unknown): boolean =>
isHttpEmbeddingError(e) &&
e instanceof Error &&
e.message.includes('returned an unparseable response'); Try / catch
// The library already retries inside resilientFetch; at the outer boundary,
// treat a persistent unparseable body as a configuration fault.
try {
await httpEmbed(texts);
} catch (e) {
if (isHttpEmbeddingError(e) && e.message.includes('unparseable response')) {
// surface to operator: endpoint is returning a non-JSON body
}
throw e;
} Prevention
- curl the endpoint once to confirm it returns application/json before launching a long indexing run.
- Point GITNEXUS_EMBEDDING_URL at the API base (e.g. .../v1), not a homepage or console.
- If you front the endpoint with a proxy, disable HTML error pages for the embeddings route.
When it happens
Trigger: httpEmbedBatch's fetchImpl: attemptResp.json() rejects and the rejection is not a terminal network error. Concrete cases: baseUrl points at a generic web server returning 200 + HTML; a reverse proxy returns a 200 status JSON-wrap error page; a load balancer truncates the streaming body mid-vector; the endpoint gzips the body and a transparent proxy strips the Content-Encoding header.
Common situations: Wrong GITNEXUS_EMBEDDING_URL pointing at the provider's homepage or console instead of the /v1 endpoint. Corporate captive portal intercepting HTTPS. Misconfigured ingress returning a friendly HTML 200 page. Intermittent upstream truncation under load (the retry loop usually masks this).
Related errors
- Embedding endpoint returned an unexpected response shape (${
- Embedding endpoint returned ${received} vectors for ${expect
- Embedding endpoint circuit open (${safeUrl(url)}, batch ${ba
- ${err.terminalMessage}
- Embedding request timed out after ${timeoutMs}ms (${safeUrl(
AI-assisted analysis of abhigyanpatwari/GitNexus@d540b00184 (2026-08-12).
Data as JSON: /api/errors/e08cb76fa8bcf040.
Report an issue: GitHub.