{"record":{"id":"ab591f41b5c2f51f","repo":"ruvnet/ruflo","slug":"invalid-item-id-id","errorCode":null,"errorMessage":"Invalid item ID: ${id}","messagePattern":"Invalid item ID: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"v3/@claude-flow/cli/src/services/registry-api.ts","lineNumber":125,"sourceCode":"\n  if (!response.ok) {\n    throw new Error('Failed to get ratings');\n  }\n\n  return response.json() as Promise<RatingResponse>;\n}\n\n/**\n * Get ratings for multiple items (batch)\n */\nexport async function getBulkRatings(\n  itemIds: string[],\n  itemType: 'plugin' | 'model' = 'plugin'\n): Promise<BulkRatingsResponse> {\n  // Validate all IDs\n  for (const id of itemIds) {\n    if (!validateItemId(id)) {\n      throw new Error(`Invalid item ID: ${id}`);\n    }\n  }\n\n  // Limit batch size\n  const limitedIds = itemIds.slice(0, 50);\n\n  const response = await fetch(`${REGISTRY_API_URL}?action=bulk-ratings`, {\n    method: 'POST',\n    headers: { 'Content-Type': 'application/json' },\n    body: JSON.stringify({\n      itemIds: limitedIds,\n      itemType,\n    }),\n    signal: AbortSignal.timeout(15000),\n  });\n\n  if (!response.ok) {\n    throw new Error('Failed to get bulk ratings');","sourceCodeStart":107,"sourceCodeEnd":143,"githubUrl":"https://github.com/ruvnet/ruflo/blob/fa13ee4ad60ac2090b1480656eb233521790d640/v3/@claude-flow/cli/src/services/registry-api.ts#L107-L143","documentation":"Thrown by getBulkRatings() during its pre-flight loop when at least one entry of itemIds fails the same validateItemId() regex (/^[@a-zA-Z0-9\\/_-]+$/, length < 100). The offending ID is included in the message, making this the diagnostic version of error 422. Validation runs before the batch is truncated to 50, so a bad ID anywhere in the array aborts the whole call.","triggerScenarios":"Calling getBulkRatings(list) where list was aggregated from user input, a config file, or package metadata and one entry contains a dot, space, '@version' suffix, or is ≥100 chars. Zero-length arrays pass validation but produce a pointless request.","commonSituations":"Mixing npm identifiers (name@version) with registry IDs in one batch; list built by string-splitting that leaves empty strings ('' fails the regex); IDs read from a CSV/JSON with invisible whitespace or BOM; one stale entry from an old schema poisoning every batch.","solutions":["Filter the list before calling — keep only IDs matching /^[@a-zA-Z0-9/_-]+$/ with length < 100 — and log the dropped ones.","The message names the bad ID: parse it out (after 'Invalid item ID: ') to pinpoint the producer of the bad entry and fix it at the source.","Trim whitespace and reject empty strings when building the array.","Remember the API slices to the first 50 IDs: chunk your calls at ≤50 so silent truncation doesn't hide missing ratings."],"exampleFix":"// before\nconst ratings = await getBulkRatings(allPluginIds); // one bad id aborts everything\n\n// after\nconst ITEM_ID_RE = /^[@a-zA-Z0-9/_-]+$/;\nconst validIds = allPluginIds.filter(id => ITEM_ID_RE.test(id) && id.length < 100);\nconst invalid = allPluginIds.filter(id => !validIds.includes(id));\nif (invalid.length) console.warn('Skipping invalid registry ids:', invalid);\nconst ratings: BulkRatingsResponse = {};\nfor (let i = 0; i < validIds.length; i += 50) {\n  Object.assign(ratings, await getBulkRatings(validIds.slice(i, i + 50)));\n}","handlingStrategy":"validation","validationCode":"const ITEM_ID_RE = /^[@a-zA-Z0-9/_-]+$/;\nfunction partitionIds(ids: string[]) {\n  const valid = ids.filter(id => ITEM_ID_RE.test(id) && id.length < 100);\n  const invalid = ids.filter(id => !(ITEM_ID_RE.test(id) && id.length < 100));\n  return { valid, invalid };\n}\nconst { valid, invalid } = partitionIds(allIds);\nif (invalid.length) console.warn('Dropping invalid registry ids:', invalid);\nreturn getBulkRatings(valid.slice(0, 50)); // API truncates at 50 — chunk yourself","typeGuard":"function isValidItemId(id: unknown): id is string {\n  return typeof id === 'string' && /^[@a-zA-Z0-9/_-]+$/.test(id) && id.length < 100;\n}","tryCatchPattern":"try {\n  return await getBulkRatings(ids);\n} catch (e) {\n  const m = e instanceof Error ? e.message.match(/^Invalid item ID: (.+)$/) : null;\n  if (m) {\n    const bad = m[1];\n    return getBulkRatings(ids.filter(id => id !== bad)); // drop offender, retry\n  }\n  throw e;\n}","preventionTips":["Sanitize the whole array at build time — one bad ID aborts the entire batch otherwise.","Split batches into chunks of ≤50; the API silently drops anything past the 50th ID.","Trim whitespace and drop empty strings when assembling ID lists from config/user input.","Log dropped IDs with their source so the producer of bad IDs gets fixed, not just filtered."],"tags":["validation","item-id","batch","registry-api","input-validation"],"backgroundTag":"input-validation-failed","analyzedSha":"fa13ee4ad60ac2090b1480656eb233521790d640","analyzedAt":"2026-08-18T21:34:22.708Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}