BloopAI/vibe-kanban · error

Fallback response for "${table}" is not an object

Error message

Fallback response for "${table}" is not an object

What it means

extractFallbackRows validates the JSON payload returned by a table's fallback endpoint: it must be an object, because the row array is looked up under the payload[table] key. This error is thrown when the fallback HTTP endpoint succeeded (2xx) but its body is not a JSON object (e.g. null, an array, a string, or invalid shape parsed as a primitive).

Source

Thrown at packages/web-core/src/shared/lib/electric/collections.ts:412

  for (const row of rows) {
    syncParams.write({
      type: 'insert',
      value: row,
      metadata: {},
    });
  }

  syncParams.commit();
  syncParams.markReady();
}

function extractFallbackRows(
  payload: unknown,
  table: string
): Array<ElectricRow> {
  if (!payload || typeof payload !== 'object') {
    throw new Error(`Fallback response for "${table}" is not an object`);
  }

  const rows = (payload as Record<string, unknown>)[table];
  if (!Array.isArray(rows)) {
    throw new Error(`Fallback response missing "${table}" array`);
  }

  return rows as Array<ElectricRow>;
}

async function parseResponseError(
  response: Response,
  fallbackMessage: string
): Promise<string> {
  try {
    const body = (await response.json()) as {
      message?: string;
      error?: string;

View on GitHub (pinned to 4deb7eca8f)

Solutions

  1. Make the fallback endpoint return { "<table>": [ ...rows ] } keyed by the shape's table name.
  2. Verify the `table` value in the ShapeDefinition matches the key the server uses in the response.
  3. Log the actual payload on failure to see what the endpoint returned.
  4. Check for proxies/middleware rewriting successful responses (auth walls returning HTML with 200).

Example fix

// before (server)
res.json(rows);
// after (server)
res.json({ tasks: rows }); // key must equal the shape's table name
Defensive patterns

Strategy: type-guard

Validate before calling

const res = await fetch(fallbackUrl);
const payload = await res.json();
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
  throw new Error('Fallback endpoint must return an object keyed by table name');
}

Type guard

function isFallbackPayload(payload: unknown, table: string): payload is Record<string, ElectricRow[]> {
  return (
    typeof payload === 'object' && payload !== null && !Array.isArray(payload) &&
    Array.isArray((payload as Record<string, unknown>)[table])
  );
}

Try / catch

try {
  const payload = await response.json();
  if (!isFallbackPayload(payload, table)) {
    console.error('Unexpected fallback payload', payload);
    return;
  }
} catch (e) {
  reportSyncError(e);
}

Prevention

When it happens

Trigger: Fallback endpoint returns a bare JSON array instead of { [table]: [...] }; endpoint returns null; endpoint returns plain text/HTML with 200; response.json() parses to a primitive; a proxy intercepts and returns a non-standard body.

Common situations: Backend route returning `res.json(rows)` instead of keyed object; API gateway/error page served with 200; mismatch between the shape's `table` name and the key the server actually uses (e.g. table 'tasks' but server returns { items: [...] }); server upgrade changed response envelope.

Related errors


AI-assisted analysis of BloopAI/vibe-kanban@4deb7eca8f (2026-08-29). Data as JSON: /api/errors/a2dc478b3e7de544. Report an issue: GitHub.