ruvnet/ruflo · error · Error

Failed to get bulk ratings

Error message

Failed to get bulk ratings

What it means

Thrown by getBulkRatings() when the POST to publish-registry?action=bulk-ratings returns non-2xx. This call has the longest budget in the file (AbortSignal.timeout(15000)) because it ships up to 50 IDs, so hitting this error means the server answered within 15s but with an error status. No response body is surfaced.

Source

Thrown at v3/@claude-flow/cli/src/services/registry-api.ts:143

      throw new Error(`Invalid item ID: ${id}`);
    }
  }

  // Limit batch size
  const limitedIds = itemIds.slice(0, 50);

  const response = await fetch(`${REGISTRY_API_URL}?action=bulk-ratings`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      itemIds: limitedIds,
      itemType,
    }),
    signal: AbortSignal.timeout(15000),
  });

  if (!response.ok) {
    throw new Error('Failed to get bulk ratings');
  }

  return response.json() as Promise<BulkRatingsResponse>;
}

/**
 * Get analytics data
 */
export async function getAnalytics(): Promise<AnalyticsResponse> {
  const response = await fetch(`${REGISTRY_API_URL}?action=analytics`, {
    signal: AbortSignal.timeout(10000),
  });

  if (!response.ok) {
    throw new Error('Failed to get analytics');
  }

  return response.json() as Promise<AnalyticsResponse>;

View on GitHub (pinned to fa13ee4ad6)

Solutions

  1. Retry with backoff (safe read-only operation), then fall back to per-item getRating() calls or an empty result.
  2. Reduce batch size (e.g. 25 per call) to shrink the payload and narrow server-side failures.
  3. Cache bulk results with a TTL and refresh in the background instead of per-request.
  4. Check the itemId list for correctness — server-side rejection of itemType or unknown IDs only surfaces as this generic error.

Example fix

// before
const all = await getBulkRatings(ids); // single point of failure

// after
async function bulkRatingsSafe(ids: string[]): Promise<BulkRatingsResponse> {
  try {
    return await getBulkRatings(ids.slice(0, 50));
  } catch {
    const out: BulkRatingsResponse = {};
    for (const id of ids) out[id] = { average: 0, count: 0 };
    return out;
  }
}
Defensive patterns

Strategy: retry

Try / catch

async function bulkRatingsResilient(ids: string[]): Promise<BulkRatingsResponse> {
  const out: BulkRatingsResponse = {};
  for (let i = 0; i < ids.length; i += 50) {
    const chunk = ids.slice(i, i + 50);
    for (let attempt = 0; ; attempt++) {
      try { Object.assign(out, await getBulkRatings(chunk)); break; }
      catch (e) {
        if (e instanceof Error && e.message.startsWith('Invalid item ID')) throw e; // fix input, don't retry
        if (attempt >= 2) { chunk.forEach(id => (out[id] = { average: 0, count: 0 })); break; }
        await new Promise(r => setTimeout(r, 400 * 2 ** attempt));
      }
    }
  }
  return out;
}

Prevention

When it happens

Trigger: Server rejects the batch payload (unknown itemType, oversized list server-side), returns 429 under rate limiting, or 5xx during cold start; a proxy intercepts the POST; intermittent failures appear only with large batches near the 50-item cap.

Common situations: Dashboard fetching ratings for dozens of plugins every page load and tripping quotas; a registry deploy temporarily breaking the bulk action; environments where only small POSTs pass a WAF/body-size filter.

Related errors


AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18). Data as JSON: /api/errors/8893e6bfa48857e9. Report an issue: GitHub.