{"record":{"id":"9699084ca49e0012","repo":"abhigyanpatwari/GitNexus","slug":"embedding-endpoint-returned-received-vectors-fo","errorCode":null,"errorMessage":"Embedding endpoint returned ${received} vectors for ${expected} texts (${safeEndpoint}, batch ${batchIndex})","messagePattern":"Embedding endpoint returned (.+?) vectors for (.+?) texts \\((.+?), batch (.+?)\\)","errorType":"exception","errorClass":"RetryableEmbeddingBodyError","httpStatus":null,"severity":"error","filePath":"gitnexus/src/core/embeddings/http-client.ts","lineNumber":522,"sourceCode":"            // response\" so they never reach for the timeout knob.\n            // Opt-in `GITNEXUS_EMBEDDING_RETRY_TIMEOUTS=1` is the exception:\n            // TimeoutError is re-wrapped so the existing retry loop can retry it.\n            throwIfRetryableTimeout(err, retryTimeouts, requestOptions.signal?.aborted, timeoutMs);\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: {\n          maxAttempts,\n          baseDelayMs: HTTP_RETRY_BACKOFF_MS,\n          capDelayMs: retryCapMs,\n          retryAfterCapMs: retryCapMs,\n          sleep: (ms) => abortableSleep(ms, requestOptions.signal),\n        },\n      },\n    );\n  } catch (err) {\n    if (","sourceCodeStart":504,"sourceCodeEnd":540,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/ac9a4e9abd8fd3058c070b72c23402a4f887929a/gitnexus/src/core/embeddings/http-client.ts#L504-L540","documentation":"httpEmbedBatch cardinality check (#2790): the endpoint returned 200 with validly-shaped payload.data but data.length !== batch.length — fewer (or more) vectors than input texts. A short body is treated as a truncated body: retried with the same backoff and breaker accounting as any endpoint fault, instead of being misclassified as success. Terminal only after retries exhaust.","triggerScenarios":"An endpoint that silently drops items — e.g. empty-string inputs filtered server-side, per-item content filtering, providers that cap batch size and truncate the response, or a half-serialized response under load.","commonSituations":"Batch size larger than the provider's limit (some return only the first N), inputs containing empty strings the provider skips, or a flaky server cutting responses mid-array.","solutions":["Reduce the embedding batch size below the provider's documented maximum.","Ensure no empty/whitespace-only texts are sent in a batch (filter before embedding).","Check provider-side filtering (content policy, token limits) that drops individual inputs.","Reproduce with curl sending the same N inputs and count returned items."],"exampleFix":"// before\nconst batch = texts; // may exceed provider cap or contain ''\n// after\nconst SAFE_BATCH = 32;\nconst batch = texts.filter((t) => t.trim().length > 0);\nfor (let i = 0; i < batch.length; i += SAFE_BATCH) { /* embed slice */ }","handlingStrategy":"validation","validationCode":"// pre-split batches under the provider cap and drop empty inputs\nconst MAX_BATCH = 32;\nconst clean = texts.filter((t) => typeof t === 'string' && t.trim().length > 0);\nconst chunks = [];\nfor (let i = 0; i < clean.length; i += MAX_BATCH) chunks.push(clean.slice(i, i + MAX_BATCH));","typeGuard":"const isCountMismatch = (e: unknown): boolean =>\n  e instanceof Error && /returned \\d+ vectors for \\d+ texts/.test(e.message);","tryCatchPattern":"try {\n  vectors = await httpEmbed(batch);\n} catch (e) {\n  if (isCountMismatch(e)) {\n    const [got, want] = e.message.match(/(\\d+) vectors for (\\d+)/)!.slice(1).map(Number);\n    if (got < want) batch = batch.slice(0, got).length === got ? batch.slice(0, got) : batch; // shrink and re-embed remainder\n  } else throw e;\n}","preventionTips":["Know your provider's max batch size and split inputs below it.","Filter empty/whitespace strings before embedding — providers silently drop them.","Watch for provider-side content filtering dropping individual inputs."],"tags":["embeddings","http","batch","truncated-response","cardinality"],"backgroundTag":"partial-api-response","analyzedSha":"ac9a4e9abd8fd3058c070b72c23402a4f887929a","analyzedAt":"2026-08-20T23:29:22.980Z","contentChangedAt":"2026-08-20T23:29:22.980Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}