koala73/worldmonitor · error · Error
Expected an array of Sentry issues. A Sentry error body is a
Error message
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.
What it means
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.
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.
Example fix
// before
const issues = await res.json();
auditResolvedIssues(issues);
// after
if (!res.ok) throw new Error(`Sentry API ${res.status}: ${JSON.stringify(await res.json())}`);
auditResolvedIssues(await res.json()); Defensive patterns
Strategy: try-catch
Validate before calling
const body = await res.json();
if (!Array.isArray(body)) throw new Error(`Expected Sentry issue list, got: ${JSON.stringify(body).slice(0, 200)}`); Type guard
const isIssueArray = (v) => Array.isArray(v);
Try / catch
try {
auditResolvedIssues(issues);
} catch (err) {
if (err.message.startsWith('Expected an array of Sentry issues')) {
console.error('Sentry returned an error body instead of an issue list — check token and res.ok before auditing.');
process.exitCode = 1;
} else throw err;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Sentry issues response was not an array
- 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/c62f8b257a208cbb.
Report an issue: GitHub.
Appendix: source
Thrown at scripts/audit-sentry-resolve-pins.mjs:117
}
return { next: null };
}
export function classifyResolution(issue) {
const statusDetails = issue?.statusDetails;
if (statusDetails && typeof statusDetails === 'object') {
for (const pin of PIN_KINDS) {
const value = statusDetails[pin.statusDetailsKey];
if (value === undefined || value === null) continue;
return { kind: pin.kind, pinKey: pin.statusDetailsKey, pinValue: pin.describe(value) };
}
}
return { kind: 'plain', pinKey: null, pinValue: null };
}
export function auditResolvedIssues(issues) {
if (!Array.isArray(issues)) {
throw new Error(
'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.',
);
}
const violations = [];
let checked = 0;
let skippedNonResolved = 0;
for (const issue of issues) {
if (issue?.status !== 'resolved') {
skippedNonResolved += 1;
continue;
}
checked += 1;
const { kind, pinValue } = classifyResolution(issue);
if (kind === 'plain') continue;
violations.push({
shortId: issue.shortId ?? null,View on GitHub (pinned to 7d06c8633d)