{"record":{"id":"31d51eb7d86f7266","repo":"zed-industries/zed","slug":"sentry-api-returned-http-error-code-for-path","errorCode":null,"errorMessage":"Sentry API returned HTTP {error.code} for {path}: {detail}","messagePattern":"Sentry API returned HTTP (.+?) for (.+?): (.+?)","errorType":"exception","errorClass":"FetchError","httpStatus":null,"severity":"error","filePath":"script/select-sentry-crash-candidates","lineNumber":61,"sourceCode":"    return None\n\n\ndef api_get(path: str, token: str):\n    url = f\"{SENTRY_BASE_URL}{path}\"\n    request = urllib.request.Request(url)\n    request.add_header(\"Authorization\", f\"Bearer {token}\")\n    request.add_header(\"Accept\", \"application/json\")\n\n    try:\n        with urllib.request.urlopen(request, timeout=30) as response:\n            return json.loads(response.read().decode(\"utf-8\"))\n    except urllib.error.HTTPError as error:\n        body = error.read().decode(\"utf-8\", errors=\"replace\")\n        try:\n            detail = json.loads(body).get(\"detail\", body)\n        except (json.JSONDecodeError, AttributeError):\n            detail = body\n        raise FetchError(f\"Sentry API returned HTTP {error.code} for {path}: {detail}\")\n    except urllib.error.URLError as error:\n        raise FetchError(f\"Failed to connect to Sentry API: {error.reason}\")\n\n\ndef fetch_issues(token: str, organization: str, limit: int, query: str):\n    encoded_query = urllib.parse.quote(query)\n    path = (\n        f\"/organizations/{organization}/issues/\"\n        f\"?limit={limit}&sort=freq&query={encoded_query}\"\n    )\n    return api_get(path, token)\n\n\ndef fetch_latest_event(token: str, issue_id: str):\n    return api_get(f\"/issues/{issue_id}/events/latest/\", token)\n\n\ndef parse_int(value, fallback=0) -> int:","sourceCodeStart":43,"sourceCodeEnd":79,"githubUrl":"https://github.com/zed-industries/zed/blob/bc538def4545534201bbfcac4e95ac34ea6501b6/script/select-sentry-crash-candidates#L43-L79","documentation":"Raised by api_get() in script/select-sentry-crash-candidates when urllib raises HTTPError — the Sentry API answered with a 4xx/5xx. The message embeds the status code plus the 'detail' field of Sentry's JSON error body when it can be parsed, falling back to the raw body.","triggerScenarios":"GET /organizations/{org}/issues/?limit=...&sort=freq&query=... or /issues/{id}/events/latest/ returning 401 (invalid/expire token), 403 (token lacks org:read / event:read scope), 404 (wrong org slug or issue id), 400 (invalid search query syntax), or 429 (rate limited).","commonSituations":"SENTRY_TOKEN unset, revoked, or created without the needed scopes; SENTRY_ORG env var is the org name instead of the slug (or vice versa); search query uses fields not valid for the issues endpoint; looping over many issues without caching triggers 429.","solutions":["Verify the token and org: curl -H \"Authorization: Bearer $SENTRY_TOKEN\" \"https://sentry.io/api/0/organizations/$SENTRY_ORG/\" should return 200","Recreate the token with org:read, project:read and event:read scopes","On 400, simplify the query string and validate Sentry search syntax against the issues endpoint","On 429, add backoff honoring X-RateLimit-Remaining/Retry-After before retrying"],"exampleFix":"# before\npath = (f\"/organizations/{organization}/issues/\"\n        f\"?limit={limit}&sort=freq&query={encoded_query}\")\nreturn api_get(path, token)\n\n# after: surface scopes hint on the common auth failure\ntry:\n    return api_get(path, token)\nexcept FetchError as error:\n    if \"HTTP 401\" in str(error):\n        raise FetchError(\"token rejected (check SENTRY_TOKEN and its scopes)\") from error\n    raise","handlingStrategy":"try-catch","validationCode":"# Pre-flight token/org before the real queries\ndef validate_sentry_access(token: str, org: str) -> None:\n    path = f\"/organizations/{org}/\"\n    try:\n        api_get(path, token)\n    except FetchError as error:\n        raise SystemExit(f\"pre-flight failed: {error}\") from error","typeGuard":null,"tryCatchPattern":"try:\n    issues = fetch_issues(token, org, limit, query)\nexcept FetchError as error:\n    text = str(error)\n    if \"HTTP 401\" in text or \"HTTP 403\" in text:\n        sys.exit(\"SENTRY_TOKEN invalid or missing scopes\")\n    if \"HTTP 404\" in text:\n        sys.exit(f\"org {org!r} or issue not found\")\n    if \"HTTP 429\" in text:\n        time.sleep(backoff); retry()  # honor Retry-After\n    else:\n        raise","preventionTips":["Create tokens with the minimum scopes needed (org:read, project:read, event:read) and record expiry","Smoke-test org slug + token with one cheap request before batch queries","Cache responses for the duration of a run to stay under rate limits"],"tags":["python","sentry","http","api","auth"],"backgroundTag":null,"analyzedSha":"bc538def4545534201bbfcac4e95ac34ea6501b6","analyzedAt":"2026-08-16T07:30:46.435Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}