abhigyanpatwari/GitNexus · error · HttpEmbeddingError

${err.terminalMessage}

Error message

${err.terminalMessage}

What it means

An HttpEmbeddingError thrown from httpEmbedBatch's catch block when a RetryableEmbeddingBodyError (errors 123/124/125) has exhausted all retries inside resilientFetch. It surfaces the sentinel's terminalMessage verbatim (so the operator sees "unparseable response", "unexpected response shape", or the count-mismatch wording) and keeps the underlying parse error only in `cause`, ensuring the raw body text never reaches the sanitizeReason fallback or stderr. This is the terminal, user-visible form of a persistently bad 2xx body.

Source

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

          sleep: (ms) => abortableSleep(ms, requestOptions.signal),
        },
      },
    );
  } catch (err) {
    if (
      requestOptions.signal?.aborted ||
      (err instanceof DOMException && err.name === 'AbortError')
    ) {
      throw new HttpEmbeddingError(
        `Embedding request cancelled (${safeUrl(url)}, batch ${batchIndex})`,
        { cause: err },
      );
    }
    // Retries are exhausted on a bad 2xx body. Surface the message the sentinel
    // carried, keeping the underlying parse error in `cause` only — the body
    // text must never reach the `sanitizeReason` fallback and leak to stderr.
    if (err instanceof RetryableEmbeddingBodyError) {
      throw new HttpEmbeddingError(err.terminalMessage, { cause: err.cause });
    }
    if (err instanceof CircuitOpenError) {
      throw new HttpEmbeddingError(
        `Embedding endpoint circuit open (${safeUrl(url)}, batch ${batchIndex}): retry in ${Math.ceil(err.retryAfterMs / 1000)}s`,
        { cause: err },
      );
    }
    if (err instanceof DOMException && err.name === 'TimeoutError') {
      throw new HttpEmbeddingError(
        `Embedding request timed out after ${timeoutMs}ms (${safeUrl(url)}, batch ${batchIndex})`,
        { cause: err },
      );
    }
    if (err instanceof ResilientFetchExhaustedError) {
      throw new HttpEmbeddingError(
        `Embedding endpoint returned ${err.response.status} (${safeUrl(url)}, batch ${batchIndex})`,
        { cause: err },
      );

View on GitHub (pinned to d540b00184)

Solutions

  1. Read the surfaced terminalMessage: if it says "unparseable", follow error 123's fixes; "unexpected response shape" → error 124; "N vectors for M texts" → error 125.
  2. Inspect err.cause for the raw parse error if you need the underlying detail (it is intentionally kept out of the user-facing message).
  3. Because retries are exhausted, the fault is deterministic — fix the endpoint configuration/schema rather than expecting another retry to help.
  4. Run the curl probe from error 123 against the configured URL+model to reproduce the bad body in isolation.

Example fix

// before: terminalMessage says "unparseable response"
// (endpoint returns HTML)
export GITNEXUS_EMBEDDING_URL=https://provider.example.com

// after
export GITNEXUS_EMBEDDING_URL=https://provider.example.com/v1
Defensive patterns

Strategy: try-catch

Validate before calling

// No pre-call validation prevents a deterministic body fault; instead, probe once:
const r = await fetch(`${URL}/embeddings`, { /* ... */ });
try {
  const j = await r.json();
  if (!Array.isArray(j?.data)) throw new Error('bad shape');
} catch {
  throw new Error('Endpoint body is not usable; see errors 123/124/125');
}

Type guard

import { isHttpEmbeddingError } from 'gitnexus';
const isTerminalBodyError = (e: unknown): boolean =>
  isHttpEmbeddingError(e) &&
  e instanceof Error &&
  (e.message.includes('unparseable response') ||
   e.message.includes('unexpected response shape') ||
   /vectors for \d+ texts/.test(e.message));

Try / catch

try {
  await httpEmbed(texts);
} catch (e) {
  if (isTerminalBodyError(e)) {
    // retries exhausted on a bad body: this is deterministic, do not retry blindly;
    // inspect e.cause for the raw parse error, then fix the endpoint config.
  }
  throw e;
}

Prevention

When it happens

Trigger: httpEmbedBatch after resilientFetch gives up: the fetchImpl callback threw RetryableEmbeddingBodyError on every attempt (maxAttempts, default HTTP_MAX_RETRIES+1 = 3). Whichever body fault (unparseable / wrong shape / count mismatch) recurred on the final attempt determines the message text carried in err.terminalMessage.

Common situations: Persistent wrong-endpoint configuration (always returns HTML). Provider schema mismatch that never resolves on retry. Provider input-count cap always truncating. Any of errors 123/124/125 that is deterministic rather than transient.

Related errors


AI-assisted analysis of abhigyanpatwari/GitNexus@d540b00184 (2026-08-12). Data as JSON: /api/errors/d5787f6bde955c21. Report an issue: GitHub.