{"record":{"id":"f0b85a391ef3b4b7","repo":"jackwener/OpenCLI","slug":"label-returned-http-resp-status-detail","errorCode":null,"errorMessage":"${label} returned HTTP ${resp.status}${detail}","messagePattern":"(.+?) returned HTTP (.+?)(.+?)","errorType":"http","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/openalex/utils.js","lineNumber":109,"sourceCode":"    }\n    if (resp.status === 404) {\n        throw new EmptyResultError(label, `OpenAlex returned 404 for ${url}.`);\n    }\n    if (resp.status === 429) {\n        throw new CommandExecutionError(\n            `${label} returned HTTP 429 (rate limited)`,\n            'OpenAlex throttles unauthenticated traffic; wait a few seconds and retry, or set OPENALEX_MAILTO.',\n        );\n    }\n    if (!resp.ok) {\n        let detail = '';\n        try {\n            const text = await resp.text();\n            const match = text.match(/\"message\"\\s*:\\s*\"([^\"]+)\"/);\n            if (match) detail = ` (${match[1]})`;\n        }\n        catch { /* ignore */ }\n        throw new CommandExecutionError(`${label} returned HTTP ${resp.status}${detail}`);\n    }\n    let body;\n    try {\n        body = await resp.json();\n    }\n    catch (err) {\n        throw new CommandExecutionError(`${label} returned malformed JSON: ${err?.message ?? err}`);\n    }\n    return body;\n}\n\n/** Strip the `https://openalex.org/` prefix if present so columns surface just the bare id. */\nexport function bareId(value) {\n    const s = String(value ?? '').trim();\n    if (!s) return '';\n    return s.replace(/^https?:\\/\\/(?:api\\.)?openalex\\.org\\//i, '').replace(/^works\\//i, '');\n}\n","sourceCodeStart":91,"sourceCodeEnd":127,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/openalex/utils.js#L91-L127","documentation":"openalexFetch wraps every OpenAlex REST call and converts non-ok HTTP statuses (other than 404/429, which get their own errors) into a CommandExecutionError carrying the status code plus any \"message\" field extracted from the error body. It exists so callers get a consistent, labeled failure instead of a raw fetch/HTTP object. The interpolated detail comes from parsing the response text for a JSON \"message\" key.","triggerScenarios":"An OpenAlex request resolves but returns a non-ok, non-404/429 status — e.g. HTTP 400 for a malformed query string, 403 for a blocked user agent, or 5xx when OpenAlex is degraded. Any command that calls openalexFetch (works lookup by id/DOI, search listings) can hit it.","commonSituations":"A malformed filter/query parameter produces 400 from api.openalex.org; a corporate proxy or firewall returns 403; OpenAlex has a transient 5xx outage; an invalid entity id path slips past validation and yields an unexpected status.","solutions":["Read the interpolated detail in the message — it contains OpenAlex's own error \"message\" from the response body, which usually names the bad parameter.","Print/log the full URL that was fetched and validate each query parameter (filters, per-page, sort) against the OpenAlex API docs.","Retry once after a short wait in case of a transient 5xx, and check the OpenAlex status page.","Escape and re-encode user-supplied query terms with encodeURIComponent before building the URL."],"exampleFix":"// before\nconst url = `${OPENALEX_BASE}/works?filter=${rawUserFilter}`;\n// after\nconst url = `${OPENALEX_BASE}/works?filter=${encodeURIComponent(rawUserFilter)}`;","handlingStrategy":"try-catch","validationCode":"function validateOpenAlexQuery(params) {\n  for (const [k, v] of Object.entries(params)) {\n    if (v == null || v === '') throw new Error(`openalex param \"${k}\" is empty`);\n  }\n  return true;\n}","typeGuard":"function isHttpError(e) {\n  return e instanceof Error && /returned HTTP \\d+/.test(e.message);\n}","tryCatchPattern":"try {\n  const body = await openalexFetch(url, 'openalex works');\n} catch (e) {\n  const m = e.message.match(/returned HTTP (\\d+)/);\n  if (m && Number(m[1]) >= 500) { /* retry after delay */ }\n  else { /* surface e.message (contains API detail) to the user */ }\n}","preventionTips":["encodeURIComponent every user-supplied query/filter value.","Validate filters against the OpenAlex filter docs before sending.","Retry transient 5xx with exponential backoff.","Check api.openalex.org reachability before large batch runs."],"tags":["http","api","openalex","network"],"backgroundTag":"http-error-response","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}