ruvnet/ruflo · error

Failed to get ratings

Error message

Failed to get ratings

What it means

Thrown by getRating() when the GET to publish-registry?action=get-ratings&itemId=... returns a non-2xx status. Unlike rateItem(), no response body is included in the message, so this is a bare 'the ratings endpoint said no'. The fetch carries a 10s AbortSignal timeout, so reaching this line means the server responded with an error status.

Source

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

  itemId: string,
  itemType: 'plugin' | 'model' = 'plugin'
): Promise<RatingResponse> {
  if (!validateItemId(itemId)) {
    throw new Error('Invalid item ID');
  }

  const params = new URLSearchParams({
    action: 'get-ratings',
    itemId,
    itemType,
  });

  const response = await fetch(`${REGISTRY_API_URL}?${params}`, {
    signal: AbortSignal.timeout(10000),
  });

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

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

/**
 * Get ratings for multiple items (batch)
 */
export async function getBulkRatings(
  itemIds: string[],
  itemType: 'plugin' | 'model' = 'plugin'
): Promise<BulkRatingsResponse> {
  // Validate all IDs
  for (const id of itemIds) {
    if (!validateItemId(id)) {
      throw new Error(`Invalid item ID: ${id}`);
    }
  }

View on GitHub (pinned to fa13ee4ad6)

Solutions

  1. Retry with backoff on 5xx/429 — the call is a read, so retrying is safe; treat persistent 404 as 'no ratings yet'.
  2. Wrap the call and fall back to { average: 0, count: 0 } so UI rendering never depends on registry availability.
  3. Cache ratings locally with a TTL instead of fetching on every render; this also keeps you inside rate limits.
  4. Confirm the itemId is registered (it must have been published) — an unpublishable ID yields server errors, not the client-side 'Invalid item ID'.

Example fix

// before
const { average } = await getRating(pluginId); // may throw 'Failed to get ratings'

// after
async function safeRating(pluginId: string) {
  for (let attempt = 0; attempt < 3; attempt++) {
    try {
      return await getRating(pluginId);
    } catch (e) {
      if (attempt === 2) return { success: false, itemId: pluginId, average: 0, count: 0 };
      await new Promise(r => setTimeout(r, 250 * 2 ** attempt));
    }
  }
  throw new Error('unreachable');
}
Defensive patterns

Strategy: retry

Try / catch

const EMPTY = (id: string) => ({ success: false, itemId: id, average: 0, count: 0 });
async function getRatingSafe(id: string, tries = 3) {
  for (let i = 0; ; i++) {
    try { return await getRating(id); }
    catch (e) {
      if (i === tries - 1) return EMPTY(id);          // degrade, don't crash
      if (e instanceof Error && e.message === 'Invalid item ID') return EMPTY(id); // don't retry client errors
      await new Promise(r => setTimeout(r, 300 * 2 ** i));
    }
  }
}

Prevention

When it happens

Trigger: Cloud Function returns 404/500 for an unknown or never-rated itemId (some backends 404 instead of returning count: 0), 429 from rate limiting, or 5xx during cold start; an intermediary proxy returns 4xx/5xx.

Common situations: Fetching ratings for a freshly published plugin before any ratings exist; polling the endpoint in a loop and tripping rate limits; CI with proxied egress; regional outage of the us-central1 Cloud Function.

Related errors


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