abhigyanpatwari/GitNexus · error · RetryableEmbeddingBodyError

Embedding endpoint returned

Error message

Embedding endpoint returned ${received} vectors for ${expected} texts (${safeEndpoint}, batch ${batchIndex})

What it means

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.

Solutions

  1. Reduce the embedding batch size below the provider's documented maximum.
  2. Ensure no empty/whitespace-only texts are sent in a batch (filter before embedding).
  3. Check provider-side filtering (content policy, token limits) that drops individual inputs.
  4. Reproduce with curl sending the same N inputs and count returned items.

Example fix

// before
const batch = texts; // may exceed provider cap or contain ''
// after
const SAFE_BATCH = 32;
const batch = texts.filter((t) => t.trim().length > 0);
for (let i = 0; i < batch.length; i += SAFE_BATCH) { /* embed slice */ }
Defensive patterns

Strategy: validation

Validate before calling

// pre-split batches under the provider cap and drop empty inputs
const MAX_BATCH = 32;
const clean = texts.filter((t) => typeof t === 'string' && t.trim().length > 0);
const chunks = [];
for (let i = 0; i < clean.length; i += MAX_BATCH) chunks.push(clean.slice(i, i + MAX_BATCH));

Type guard

const isCountMismatch = (e: unknown): boolean =>
  e instanceof Error && /returned \d+ vectors for \d+ texts/.test(e.message);

Try / catch

try {
  vectors = await httpEmbed(batch);
} catch (e) {
  if (isCountMismatch(e)) {
    const [got, want] = e.message.match(/(\d+) vectors for (\d+)/)!.slice(1).map(Number);
    if (got < want) batch = batch.slice(0, got).length === got ? batch.slice(0, got) : batch; // shrink and re-embed remainder
  } else throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of abhigyanpatwari/GitNexus@ac9a4e9abd (2026-08-20). Data as JSON: /api/errors/9699084ca49e0012. Report an issue: GitHub.

Appendix: source

Thrown at gitnexus/src/core/embeddings/http-client.ts:522

            // response" so they never reach for the timeout knob.
            // Opt-in `GITNEXUS_EMBEDDING_RETRY_TIMEOUTS=1` is the exception:
            // TimeoutError is re-wrapped so the existing retry loop can retry it.
            throwIfRetryableTimeout(err, retryTimeouts, requestOptions.signal?.aborted, timeoutMs);
            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;
        },
        breakerKey: HTTP_BREAKER_KEY,
        retry: {
          maxAttempts,
          baseDelayMs: HTTP_RETRY_BACKOFF_MS,
          capDelayMs: retryCapMs,
          retryAfterCapMs: retryCapMs,
          sleep: (ms) => abortableSleep(ms, requestOptions.signal),
        },
      },
    );
  } catch (err) {
    if (

View on GitHub (pinned to ac9a4e9abd)