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

  1. 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.
  2. Confirm SENTRY_HOST (or the default) points at the correct Sentry instance (sentry.io vs self-hosted).
  3. Verify org/project slugs by opening the project page in Sentry's UI and copying them exactly.
  4. 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

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


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)