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

  1. Log/inspect the raw response body for one request (`curl -H 'Authorization: Bearer <token>' '<issues-url>'`) to see what shape is actually returned.
  2. Check whether a proxy or gateway (corporate proxy, Cloudflare, SSO) is rewriting the response; bypass it or allowlist the Sentry host.
  3. Verify SENTRY_HOST points at the real Sentry API host, not a wrapper.
  4. 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

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


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)