koala73/worldmonitor · error · Error
Sentry issues response was not an array
Error message
Sentry issues response was not an array
What it means
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.
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.
Example fix
// before: saving payload wrapped in an envelope
{ "issues": [ {...}, {...} ] }
// after
[ {...}, {...} ] Defensive patterns
Strategy: type-guard
Validate before calling
// validate a saved payload before running with --input
const raw = JSON.parse(readFileSync(inputPath, 'utf8'));
if (!Array.isArray(raw)) throw new Error('Saved payload must be a JSON array of issues'); Type guard
function isIssueArray(value) {
return Array.isArray(value) && value.every(
(it) => it != null && typeof it === 'object' && ('id' in it || 'shortId' in it),
);
} Try / catch
try {
const batch = await response.json();
if (!Array.isArray(batch)) {
console.error('Unexpected Sentry response:', JSON.stringify(batch).slice(0, 500));
throw new Error('Sentry issues response was not an array');
}
} catch (err) {
if (err instanceof SyntaxError) throw new Error('Sentry returned non-JSON body (proxy/gateway?)');
throw err;
} Prevention
- 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.
When it happens
Trigger: `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.
Common situations: 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.
Related errors
- Expected an array of Sentry issues. A Sentry error body is a
- Invalid scorecard bloc selection.
- Sentry issues request failed: HTTP ${response.status} ${resp
- Sentry issues pagination exceeded ${MAX_PAGES} pages
- Redis transaction failed: ${results?.error || 'invalid respo
AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-09-15).
Data as JSON: /api/errors/96c9f685eee8798c.
Report an issue: GitHub.
Appendix: source
Thrown at scripts/audit-sentry-resolve-pins.mjs:240
for (let page = 0; page < MAX_PAGES; page += 1) {
const response = await fetchImpl(url, {
headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' },
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
});
if (response.status === 403) {
throw new Error(
'Sentry returned 403 for the issues endpoint. A release-scoped `sntrys_` upload '
+ 'token cannot read issues, no matter which project it was minted for. Export a '
+ '`sntryu_` user token carrying `event:read` and `project:read` as SENTRY_AUTH_TOKEN '
+ '(or SENTRY_SESSION_TOKEN) and rerun.',
);
}
if (!response.ok) {
throw new Error(`Sentry issues request failed: HTTP ${response.status} ${response.statusText}`);
}
const batch = await response.json();
if (!Array.isArray(batch)) throw new Error('Sentry issues response was not an array');
issues.push(...batch);
const { next } = parseLinkHeader(response.headers.get('link'));
if (!next) {
assertLiveBoardIsNotEmpty(issues, org, project);
return issues;
}
url = sameOriginCursor(next);
}
throw new Error(`Sentry issues pagination exceeded ${MAX_PAGES} pages`);
}
async function main() {
const args = parseArguments(process.argv.slice(2));
const org = process.env.SENTRY_ORG || DEFAULT_ORG;
const project = process.env.SENTRY_PROJECT || DEFAULT_PROJECT;
View on GitHub (pinned to 7d06c8633d)