paperclipai/paperclip · error

${provider} returned an unreadable inventory response

Error message

${provider} returned an unreadable inventory response

What it means

Thrown by jsonResponse, the shared inventory-response helper in chat-provider-inventory.ts, when response.json() rejects — i.e. the provider (GitHub or Slack) returned a body that is not parseable JSON. The helper is used by all chat-provider inventory listings, so this guards every provider call uniformly.

Source

Thrown at server/src/services/chat-provider-inventory.ts:49

const GITHUB_API_TIMEOUT_MS = 25_000;

function slackRequestSignal(): AbortSignal {
  return AbortSignal.timeout(SLACK_API_TIMEOUT_MS);
}

function githubRequestSignal(): AbortSignal {
  return AbortSignal.timeout(GITHUB_API_TIMEOUT_MS);
}

async function jsonResponse<T>(
  response: Response,
  provider: string,
): Promise<T> {
  let body: unknown;
  try {
    body = await response.json();
  } catch {
    throw new Error(`${provider} returned an unreadable inventory response`);
  }
  if (!response.ok) {
    const message =
      body && typeof body === "object" && "message" in body
        ? String((body as { message?: unknown }).message)
        : String(response.status);
    throw new Error(`${provider} inventory failed: ${message}`);
  }
  return body as T;
}

/** List only Slack conversations where the installed bot is a member. */
export async function listSlackBotChannels(input: {
  botToken: string;
  fetch: typeof globalThis.fetch;
}): Promise<ChatProviderInventoryResult> {
  const resources: ChatProviderResourceInventoryItem[] = [];
  let cursor = "";

View on GitHub (pinned to 01ad858492)

Solutions

  1. Retry the inventory request; transient truncation usually resolves.
  2. Verify the server can reach the provider API directly (curl the endpoint) without HTML-interposing proxies.
  3. Check the provider's base URL configuration for typos or outdated endpoints.
  4. Inspect provider status pages (Slack/GitHub) for ongoing incidents.
Defensive patterns

Strategy: retry

Validate before calling

// Sanity-check reachability/JSON before inventory:
const probe = await fetch(providerUrl, { method: 'HEAD' }).catch(() => null);
if (!probe || (probe.headers.get('content-type') ?? '').includes('text/html')) {
  throw new Error('Provider endpoint unreachable or returning HTML; check proxy/base URL');
}

Try / catch

try { const inv = await listProviderResources(input); }
catch (e) {
  if (e.message.includes('unreadable inventory response')) {
    await retryWithBackoff(() => listProviderResources(input), 3); // transient bad body
  } else throw e;
}

Prevention

When it happens

Trigger: response.json() throws on: HTML error page from a proxy/captive portal, empty body, malformed JSON, or a stream read error even though the HTTP exchange completed.

Common situations: Corporate proxy or VPN intercepting provider API traffic; provider outage returning an error page with 200/5xx; network middleware truncating responses; calling through a URL that actually returns HTML (wrong base URL config).

Understand the failure class

Background: "Invalid JSON response" and "Failed to parse response" errors: when an API answers 200 but the body isn't the JSON your library expected — this error's family across 28 libraries.

Related errors


AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10). Data as JSON: /api/errors/42f87b4042422ab2. Report an issue: GitHub.