koala73/worldmonitor · error · Error
Sentry issues request failed: HTTP ${response.status} ${resp
Error message
Sentry issues request failed: HTTP ${response.status} ${response.statusText} What it means
After the specific 403 check, fetchResolvedIssues() requires every Sentry HTTP response to be ok (`response.ok`). Any other non-2xx status from the issues endpoint (401 unauthorized, 404 wrong org/project, 429 rate limited, 5xx) surfaces as this generic message embedding the status code and statusText from Sentry's API.
Solutions
- Read the HTTP status in the message: 401 → re-mint/fix the `sntryu_` token; 404 → check SENTRY_ORG and SENTRY_PROJECT slugs against the Sentry URL; 429 → retry after backoff; 5xx → retry later or check Sentry status.
- Confirm SENTRY_HOST (or the default) points at the correct Sentry instance (sentry.io vs self-hosted).
- Verify org/project slugs by opening the project page in Sentry's UI and copying them exactly.
- For persistent failures, run offline: `node scripts/audit-sentry-resolve-pins.mjs --input <saved-issues.json>`.
Example fix
// before SENTRY_PROJECT=world-monitor # wrong slug // after SENTRY_PROJECT=worldmonitor # exact slug from the Sentry project URL
Defensive patterns
Strategy: retry
Validate before calling
// preflight the endpoint before the paginated run
const probe = await fetch(`${SENTRY_HOST}/api/0/`, {
headers: { Authorization: `Bearer ${token}` },
});
if (!probe.ok) throw new Error(`Sentry preflight failed: HTTP ${probe.status}`); Try / catch
try {
issues = await fetchResolvedIssues(token, org, project);
} catch (err) {
if (/HTTP 42[09]/.test(err.message)) {
// 429/4205: back off and retry once
await new Promise(r => setTimeout(r, 30_000));
issues = await fetchResolvedIssues(token, org, project);
} else {
throw err;
}
} Prevention
- Copy org/project slugs directly from the Sentry project URL instead of typing them.
- Pin SENTRY_HOST explicitly in CI to avoid wrong-instance mistakes.
- Watch Sentry rate limits and add spacing between repeated audit runs.
- Check the message's embedded status code first — it usually names the exact fix (401 token, 404 slug, 429 rate).
When it happens
Trigger: Any page of the paginated GET to `/api/0/projects/{org}/{project}/issues/` returns a status other than 2xx and other than 403 — e.g. 401 (bad/expired token), 404 (org/project slug not found), 429 (rate limited), or 5xx (Sentry outage).
Common situations: Typo in SENTRY_ORG or SENTRY_PROJECT slugs (404); SENTRY_HOST pointed at a wrong/self-hosted URL; expired or revoked token (401); hammering the API and hitting 429; transient Sentry 5xx during an incident.
Understand the failure class
Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.
Related errors
- Redis HTTP ${resp.status}
- HTTP ${response.status}
- HTTP ${response.status}
- Expected an array of Sentry issues. A Sentry error body is a
- Sentry issues response was not an array
AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-09-15).
Data as JSON: /api/errors/0d561bdfcac6d365.
Report an issue: GitHub.
Appendix: source
Thrown at scripts/audit-sentry-resolve-pins.mjs:237
export async function fetchResolvedIssues(token, org, project, fetchImpl = globalThis.fetch) {
const issues = [];
let url = issuesUrl(org, project);
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));View on GitHub (pinned to 7d06c8633d)