{"record":{"id":"de968880dbf4948f","repo":"abhigyanpatwari/GitNexus","slug":"embedding-endpoint-returned-an-unexpected-response","errorCode":null,"errorMessage":"Embedding endpoint returned an unexpected response shape (${safeUrl(url)}, batch ${batchIndex})","messagePattern":"Embedding endpoint returned an unexpected response shape \\((.+?), batch (.+?)\\)","errorType":"exception","errorClass":"RetryableEmbeddingBodyError","httpStatus":null,"severity":"error","filePath":"gitnexus/src/core/embeddings/http-client.ts","lineNumber":463,"sourceCode":"            payload = (await attemptResp.json()) as { data: EmbeddingItem[] };\n          } catch (err) {\n            // Not every `.json()` rejection is a parse error: the per-attempt\n            // signal (`AbortSignal.any([caller, AbortSignal.timeout(...)])`) is\n            // wired to the body stream, so a stalled body rejects with the abort\n            // reason. Re-raise those untouched — `isTerminalNetworkError` is\n            // `resilientFetch`'s own predicate, so this test agrees with\n            // `classifyOutcome` by construction. Wrapping one would flip its\n            // verdict from `terminal-network` (returned without retry AND\n            // without touching the breaker, via `recordNeutral()`) to\n            // `retryable-network` (retried, then `breaker.recordFailure()`): the\n            // same timeout would take 3 attempts instead of 1, count toward the\n            // process-global `embeddings-http` breaker, and reach the operator as\n            // \"unparseable response\" so they never reach for the timeout knob.\n            if (isTerminalNetworkError(err)) throw err;\n            throw new RetryableEmbeddingBodyError(unparseableMessage(), { cause: err });\n          }\n          if (!Array.isArray(payload?.data) || !payload.data.every(isEmbeddingItem)) {\n            throw new RetryableEmbeddingBodyError(unexpectedShapeMessage());\n          }\n          // Cardinality belongs *inside* the retry loop. `every(isEmbeddingItem)`\n          // is vacuously true for `[]` and true for any array shorter than the\n          // request, so a 200 carrying `{\"data\": []}` — or half the vectors —\n          // used to be classified `success`, call `breaker.recordSuccess()`\n          // (erasing the outage signal), and only then fail terminally after a\n          // single attempt. A short body is a truncated body: same backoff, same\n          // breaker accounting as any other endpoint fault (#2790).\n          if (payload.data.length !== batch.length) {\n            throw new RetryableEmbeddingBodyError(\n              countMismatchMessage(payload.data.length, batch.length, safeUrl(url), batchIndex),\n            );\n          }\n          parsed = payload.data;\n          return attemptResp;\n        },\n        breakerKey: HTTP_BREAKER_KEY,\n        retry: {","sourceCodeStart":445,"sourceCodeEnd":481,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/d540b00184d71a896261ee02670da9a92d59d8f7/gitnexus/src/core/embeddings/http-client.ts#L445-L481","documentation":"A module-private RetryableEmbeddingBodyError thrown inside the retried fetch callback when the body parses as JSON but is not the expected `{ data: EmbeddingItem[] }` shape — specifically when `payload.data` is not an array, or not every element is an `{ embedding: number[] }` item (isEmbeddingItem). Like error 123 it is retried and counted against the circuit breaker, then surfaced terminally as HttpEmbeddingError (127) on exhaustion.","triggerScenarios":"httpEmbedBatch's fetchImpl after a successful .json(): the parsed object has no `data` array (e.g. `{ \"error\": \"...\" }` returned with HTTP 200), `data` is an object instead of an array, or `data` contains items shaped wrong (e.g. `{ \"embedding\": \"0.1,0.2\" }` string instead of number array, or `{ \"vector\": [...] }` with the wrong key, or `null` entries).","commonSituations":"Endpoint returns a JSON error envelope with HTTP 200 (some proxies/gateways do this). Provider uses a non-OpenAI response schema (e.g. Cohere's `{ embeddings: [...] }` or a custom `{ vectors: [...] }`). Provider returns embedding as a base64 string or comma-separated string instead of a JSON number array. A `null` sneaks into the data array.","solutions":["Confirm the endpoint is OpenAI-compatible: it must return `{ \"data\": [{ \"embedding\": [0.1, ...] }, ...] }`. Inspect a raw response with curl.","If the provider uses a different schema, front it with a thin adapter that reshapes the response into the OpenAI shape.","If the body occasionally contains error envelopes, fix the upstream gateway to use proper non-2xx status codes; the retry loop tolerates transient corruption but persistent 200-errors will exhaust it.","Check that the model name in GITNEXUS_EMBEDDING_MODEL is valid for the endpoint — some providers return a 200 JSON error for an unknown model."],"exampleFix":"// before: provider returns { \"vectors\": [...] }\n// (no env fix; requires an adapter in front of the endpoint)\n\n// after: endpoint returns OpenAI shape\n// { \"data\": [{ \"embedding\": [0.1, 0.2, ...] }] }","handlingStrategy":"validation","validationCode":"// Verify the OpenAI shape on a probe response:\nconst r = await fetch(`${URL}/embeddings`, { /* ... */ });\nconst body = await r.json();\nconst ok =\n  Array.isArray(body?.data) &&\n  body.data.every((it: unknown) =>\n    it !== null && typeof it === 'object' &&\n    Array.isArray((it as any).embedding) &&\n    (it as any).embedding.every((n: unknown) => typeof n === 'number'));\nif (!ok) throw new Error('Endpoint response is not OpenAI-shaped { data: [{ embedding: number[] }] }');","typeGuard":"import { isHttpEmbeddingError } from 'gitnexus';\nconst isUnexpectedShape = (e: unknown): boolean =>\n  isHttpEmbeddingError(e) &&\n  e instanceof Error &&\n  e.message.includes('unexpected response shape');","tryCatchPattern":"try {\n  await httpEmbed(texts);\n} catch (e) {\n  if (isHttpEmbeddingError(e) && e.message.includes('unexpected response shape')) {\n    // provider schema is not OpenAI-compatible; front with an adapter\n  }\n  throw e;\n}","preventionTips":["Use an OpenAI-compatible endpoint or wrap a non-standard provider in a reshaping adapter.","Reject providers that return JSON error envelopes with HTTP 200.","Probe once at startup with the isEmbeddingItem-shaped validator above."],"tags":["endpoint","response-body","retry","schema","embeddings"],"backgroundTag":null,"analyzedSha":"d540b00184d71a896261ee02670da9a92d59d8f7","analyzedAt":"2026-08-12T19:50:25.132Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}