{"record":{"id":"96c9f685eee8798c","repo":"koala73/worldmonitor","slug":"sentry-issues-response-was-not-an-array","errorCode":null,"errorMessage":"Sentry issues response was not an array","messagePattern":"Sentry issues response was not an array","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/audit-sentry-resolve-pins.mjs","lineNumber":240,"sourceCode":"\n  for (let page = 0; page < MAX_PAGES; page += 1) {\n    const response = await fetchImpl(url, {\n      headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' },\n      signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),\n    });\n    if (response.status === 403) {\n      throw new Error(\n        'Sentry returned 403 for the issues endpoint. A release-scoped `sntrys_` upload '\n          + 'token cannot read issues, no matter which project it was minted for. Export a '\n          + '`sntryu_` user token carrying `event:read` and `project:read` as SENTRY_AUTH_TOKEN '\n          + '(or SENTRY_SESSION_TOKEN) and rerun.',\n      );\n    }\n    if (!response.ok) {\n      throw new Error(`Sentry issues request failed: HTTP ${response.status} ${response.statusText}`);\n    }\n    const batch = await response.json();\n    if (!Array.isArray(batch)) throw new Error('Sentry issues response was not an array');\n    issues.push(...batch);\n\n    const { next } = parseLinkHeader(response.headers.get('link'));\n    if (!next) {\n      assertLiveBoardIsNotEmpty(issues, org, project);\n      return issues;\n    }\n    url = sameOriginCursor(next);\n  }\n\n  throw new Error(`Sentry issues pagination exceeded ${MAX_PAGES} pages`);\n}\n\nasync function main() {\n  const args = parseArguments(process.argv.slice(2));\n  const org = process.env.SENTRY_ORG || DEFAULT_ORG;\n  const project = process.env.SENTRY_PROJECT || DEFAULT_PROJECT;\n","sourceCodeStart":222,"sourceCodeEnd":258,"githubUrl":"https://github.com/koala73/worldmonitor/blob/7d06c8633d256c18e38133030bc3613976a96ec9/scripts/audit-sentry-resolve-pins.mjs#L222-L258","documentation":"fetchResolvedIssues() parses each page's body as JSON and requires it to be an array of issues. If the body parses but is not an array (e.g. an error/detail object, a wrapped envelope, or an HTML error page that happens to parse), this guard throws instead of letting `issues.push(...batch)` corrupt the audit.","triggerScenarios":"`await response.json()` on a page of the issues endpoint returns a non-array — e.g. `{detail: '...'}` error object with a 2xx status, a proxy/WAF HTML or JSON error envelope, or a Sentry API version change altering the response shape.","commonSituations":"A reverse proxy, auth gateway, or captive portal intercepting the request and returning a JSON error object with 200; pointing SENTRY_HOST at a non-Sentry endpoint; a future Sentry API response-shape change; saving a payload by hand and wrapping it in an object.","solutions":["Log/inspect the raw response body for one request (`curl -H 'Authorization: Bearer <token>' '<issues-url>'`) to see what shape is actually returned.","Check whether a proxy or gateway (corporate proxy, Cloudflare, SSO) is rewriting the response; bypass it or allowlist the Sentry host.","Verify SENTRY_HOST points at the real Sentry API host, not a wrapper.","If a saved --input payload was hand-edited, restore it to a plain array of issues."],"exampleFix":"// before: saving payload wrapped in an envelope\n{ \"issues\": [ {...}, {...} ] }\n// after\n[ {...}, {...} ]","handlingStrategy":"type-guard","validationCode":"// validate a saved payload before running with --input\nconst raw = JSON.parse(readFileSync(inputPath, 'utf8'));\nif (!Array.isArray(raw)) throw new Error('Saved payload must be a JSON array of issues');","typeGuard":"function isIssueArray(value) {\n  return Array.isArray(value) && value.every(\n    (it) => it != null && typeof it === 'object' && ('id' in it || 'shortId' in it),\n  );\n}","tryCatchPattern":"try {\n  const batch = await response.json();\n  if (!Array.isArray(batch)) {\n    console.error('Unexpected Sentry response:', JSON.stringify(batch).slice(0, 500));\n    throw new Error('Sentry issues response was not an array');\n  }\n} catch (err) {\n  if (err instanceof SyntaxError) throw new Error('Sentry returned non-JSON body (proxy/gateway?)');\n  throw err;\n}","preventionTips":["Log the first 200-500 chars of an unexpected body to identify proxy/SSO interference quickly.","Ensure corporate proxies/SSO gateways allowlist the Sentry API host.","Never hand-edit saved --input payloads; regenerate them from the live script.","Keep SENTRY_HOST pointed at the canonical API host rather than a dashboard URL."],"tags":["sentry","api","response-shape","validation"],"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"}