thedotmack/claude-mem · error · ServerClassifiedProviderError

parse_error

parse_error

Error message

Anthropic returned invalid JSON

What it means

Thrown by ClaudeObservationProvider.generate() when response.json() rejects after a 2xx HTTP response from Anthropic. Classified as parse_error, it indicates the body was not valid JSON — typically an HTML error page, an empty body, or a truncated/intercepted response. The thrown error carries the underlying parse error as its cause.

Source

Thrown at src/server/generation/providers/ClaudeObservationProvider.ts:98

      });
    }

    if (!response.ok) {
      const bodyText = await safeReadBody(response);
      throw classifyClaudeServerError({
        status: response.status,
        bodyText,
        headers: response.headers,
        cause: new Error(`Anthropic API error: ${response.status} - ${bodyText}`),
      });
    }

    let data: AnthropicMessagesResponse;
    try {
      data = (await response.json()) as AnthropicMessagesResponse;
    } catch (parseError) {
      const err = parseError instanceof Error ? parseError : new Error(String(parseError));
      throw new ServerClassifiedProviderError('Anthropic returned invalid JSON', {
        kind: 'parse_error',
        cause: err,
      });
    }

    if (data.error) {
      throw classifyClaudeServerError({
        status: response.status,
        bodyText: `${data.error.type ?? ''} ${data.error.message ?? ''}`,
        headers: response.headers,
        cause: new Error(`Anthropic API error: ${data.error.type} - ${data.error.message}`),
      });
    }

    const blocks = Array.isArray(data.content) ? data.content : [];
    const rawText = blocks
      .filter(block => block?.type === 'text' && typeof block.text === 'string')
      .map(block => block.text!)

View on GitHub (pinned to d768ba3643)

Solutions

  1. Inspect the caught error's cause and the prior status/bodyText logged just before the parse — non-2xx is handled separately, so a 2xx with bad JSON points at interception.
  2. Re-run; transient HTML interception (WAF/proxy) often clears on retry.
  3. If behind a proxy, bypass it for api.anthropic.com or add it to the allowlist.
  4. Confirm the configured base URL resolves to the real Anthropic API host.
  5. If persistent, capture the raw body to confirm whether it is HTML/empty, then report upstream.

Example fix

// before: only status checked, JSON parse throws unexpectedly
// after: capture bodyText for diagnostics before parsing
const bodyText = await response.text();
if (!bodyText.trim().startsWith('{')) {
  throw new Error('Anthropic returned non-JSON body');
}
const data = JSON.parse(bodyText) as AnthropicMessagesResponse;
Defensive patterns

Strategy: retry

Validate before calling

async function fetchJsonOrThrow(response: Response): Promise<unknown> {
  const bodyText = await response.text();
  if (!response.ok) {
    throw new Error(`Anthropic API error: ${response.status} - ${bodyText}`);
  }
  if (!bodyText || !bodyText.trim().startsWith('{')) {
    throw new Error('Anthropic returned a non-JSON body (possible proxy interception)');
  }
  return JSON.parse(bodyText);
}

Type guard

function isAnthropicMessagesResponse(v: unknown): v is AnthropicMessagesResponse {
  return typeof v === 'object' && v !== null
    && Array.isArray((v as { content?: unknown }).content);
}

Try / catch

for (let attempt = 0; attempt < 3; attempt++) {
  try {
    return await provider.generate(context);
  } catch (error) {
    const isParse = error instanceof ServerClassifiedProviderError && error.kind === 'parse_error';
    if (!isParse || attempt === 2) throw error;
    await new Promise(r => setTimeout(r, 2 ** attempt * 500)); // backoff
  }
}

Prevention

When it happens

Trigger: A corporate proxy or WAF returns an HTML block page instead of JSON; the upstream returns 200 with an empty or partial body; a gateway truncates the stream; a rate-limit intermediary returns a non-JSON challenge; the endpoint URL points at the wrong host.

Common situations: Behind a corporate proxy that rewrites responses. Anthropic serves a temporary HTML status page during an incident. A misconfigured base URL hits a CDN edge returning HTML. Network instability truncates the body mid-stream.

Understand the failure class

Related errors


AI-assisted analysis of thedotmack/claude-mem@d768ba3643 (2026-08-12). Data as JSON: /api/errors/0de29a7bdf61354d. Report an issue: GitHub.