{"record":{"id":"c62f8b257a208cbb","repo":"koala73/worldmonitor","slug":"expected-an-array-of-sentry-issues-a-sentry-error-body-is-an","errorCode":null,"errorMessage":"Expected an array of Sentry issues. A Sentry error body is an object, so coercing this to an empty list would report an authentication failure as a clean board.","messagePattern":"Expected an array of Sentry issues\\. A Sentry error body is an object, so coercing this to an empty list would report an authentication failure as a clean board\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/audit-sentry-resolve-pins.mjs","lineNumber":117,"sourceCode":"  }\n  return { next: null };\n}\n\nexport function classifyResolution(issue) {\n  const statusDetails = issue?.statusDetails;\n  if (statusDetails && typeof statusDetails === 'object') {\n    for (const pin of PIN_KINDS) {\n      const value = statusDetails[pin.statusDetailsKey];\n      if (value === undefined || value === null) continue;\n      return { kind: pin.kind, pinKey: pin.statusDetailsKey, pinValue: pin.describe(value) };\n    }\n  }\n  return { kind: 'plain', pinKey: null, pinValue: null };\n}\n\nexport function auditResolvedIssues(issues) {\n  if (!Array.isArray(issues)) {\n    throw new Error(\n      'Expected an array of Sentry issues. A Sentry error body is an object, so coercing this '\n        + 'to an empty list would report an authentication failure as a clean board.',\n    );\n  }\n  const violations = [];\n  let checked = 0;\n  let skippedNonResolved = 0;\n\n  for (const issue of issues) {\n    if (issue?.status !== 'resolved') {\n      skippedNonResolved += 1;\n      continue;\n    }\n    checked += 1;\n    const { kind, pinValue } = classifyResolution(issue);\n    if (kind === 'plain') continue;\n    violations.push({\n      shortId: issue.shortId ?? null,","sourceCodeStart":99,"sourceCodeEnd":135,"githubUrl":"https://github.com/koala73/worldmonitor/blob/7d06c8633d256c18e38133030bc3613976a96ec9/scripts/audit-sentry-resolve-pins.mjs#L99-L135","documentation":"auditResolvedIssues insists its input is an array of Sentry issues. When the Sentry API returns an error body (an object, e.g. {detail: \"Invalid token\"}), coercing it to an empty array would make a broken API call look like a successfully resolved board. The script throws instead so failures are loud, never silently reported as clean.","triggerScenarios":"Passing the parsed JSON of a Sentry HTTP error response (authentication failure, 403, rate-limit body) — or any non-array value such as an object or null — to auditResolvedIssues.","commonSituations":"An expired or revoked auth token returns {detail: \"Authentication credentials were not provided.\"}; a wrong org/project slug returns an error object; a caller skips checking response.ok and feeds the error body straight to the audit.","solutions":["Check the HTTP status before parsing: only pass await response.json() to auditResolvedIssues when response.ok is true.","Verify the Sentry token is valid and has access to the org/project that produced the error body.","Log the raw response body on non-OK status to see the actual API error (invalid token, wrong slug, rate limit) before auditing.","Restructure the caller so error bodies throw or exit non-zero before reaching auditResolvedIssues."],"exampleFix":"// before\nconst issues = await res.json();\nauditResolvedIssues(issues);\n// after\nif (!res.ok) throw new Error(`Sentry API ${res.status}: ${JSON.stringify(await res.json())}`);\nauditResolvedIssues(await res.json());","handlingStrategy":"try-catch","validationCode":"const body = await res.json();\nif (!Array.isArray(body)) throw new Error(`Expected Sentry issue list, got: ${JSON.stringify(body).slice(0, 200)}`);","typeGuard":"const isIssueArray = (v) => Array.isArray(v);","tryCatchPattern":"try {\n  auditResolvedIssues(issues);\n} catch (err) {\n  if (err.message.startsWith('Expected an array of Sentry issues')) {\n    console.error('Sentry returned an error body instead of an issue list — check token and res.ok before auditing.');\n    process.exitCode = 1;\n  } else throw err;\n}","preventionTips":["Always check response.ok before calling response.json() on Sentry responses.","Validate that the token works with a lightweight API call before running the audit.","Fail loudly (non-zero exit) on non-array bodies instead of coercing to [].","Log the raw error body on failure so auth/slug problems are visible."],"tags":["sentry","validation","api"],"backgroundTag":"unexpected-response-shape","analyzedSha":"7d06c8633d256c18e38133030bc3613976a96ec9","analyzedAt":"2026-09-15T16:44:39.439Z","contentChangedAt":"2026-09-15T16:44:39.439Z","schemaVersion":2},"datasetVersion":"2026-09-15T18:17:12.389Z"}