{"record":{"id":"bfd03854ddcad548","repo":"stablyai/orca","slug":"gitlab-returned-an-error-reported","errorCode":null,"errorMessage":"GitLab returned an error: ${reported}","messagePattern":"GitLab returned an error: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/main/gitlab/glab-api-response.ts","lineNumber":47,"sourceCode":"export class GlabNonListResponseError extends Error {}\n\n// Why: glab allows a 10MB body; this is what keeps a proxy's whole response out of the error banner.\nconst REPORTED_PAYLOAD_LIMIT = 300\n\n/**\n * Parse a glab list response, failing readably when GitLab answers with a JSON object.\n *\n * Why: glab exits 0 on error envelopes and proxy wrappers, so `JSON.parse(...).map` threw an\n * opaque `.map is not a function` that the caller's classifier could only report as \"unknown\".\n */\nexport function parseGlabJsonList<T>(payload: string): T[] {\n  const parsed: unknown = JSON.parse(payload)\n  if (Array.isArray(parsed)) {\n    return parsed as T[]\n  }\n  const reported = gitlabErrorText(parsed)\n  if (reported) {\n    throw new Error(`GitLab returned an error: ${reported}`)\n  }\n  // Why: slice the raw payload rather than re-serializing `parsed` — same text, without\n  // stringifying a multi-megabyte body just to keep the preview.\n  throw new GlabNonListResponseError(\n    `GitLab returned a non-list response: ${payload.trim().slice(0, REPORTED_PAYLOAD_LIMIT)}`\n  )\n}\n\n/** GitLab reports API failures as `{ message }` or `{ error }`; anything else is opaque data. */\nfunction gitlabErrorText(parsed: unknown): string | null {\n  if (typeof parsed !== 'object' || parsed === null) {\n    return null\n  }\n  const { message, error } = parsed as { message?: unknown; error?: unknown }\n  for (const value of [message, error]) {\n    if (typeof value === 'string' && value.trim()) {\n      return value.trim().slice(0, REPORTED_PAYLOAD_LIMIT)\n    }","sourceCodeStart":29,"sourceCodeEnd":65,"githubUrl":"https://github.com/stablyai/orca/blob/1136503c6a231a16dce8f921f6fadb63d181e8db/src/main/gitlab/glab-api-response.ts#L29-L65","documentation":"Thrown by parseGlabJsonList when glab returned a JSON object (not an array) that carries a GitLab error envelope — specifically an object with a string `message` or `error` field. glab exits 0 on many error envelopes (and on proxy interstitials), so without this guard `JSON.parse(...).map` would throw an opaque `.map is not a function`. The reported text is truncated to REPORTED_PAYLOAD_LIMIT (300 chars).","triggerScenarios":"Any glab list command (`glab api projects/:id/merge_requests`, `glab mr list`, etc.) where GitLab responds with `{ \"message\": \"...\" }` or `{ \"error\": \"...\" }` but glab still exits 0. Common with 401/403/404 envelopes, rate-limit notices, and reverse-proxy error pages that pass through as JSON.","commonSituations":"Expired or revoked GitLab token (401 envelope); insufficient scope (403); project path/MR iid mismatch (404); GitLab self-hosted behind a proxy that returns its own JSON error; rate limiting (`{ message: \"429 Too Many Requests\" }`).","solutions":["Read the reported text — it is the literal GitLab message and names the real cause (auth, scope, not-found, rate limit).","Refresh the GitLab token / reconnect the integration if the message indicates 401/403.","Verify the project path and endpoint path are correct for the target GitLab instance.","If behind a proxy, confirm the proxy forwards the real GitLab response rather than its own error envelope."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"function looksLikeGitLabErrorEnvelope(payload: string): boolean {\n  try {\n    const v = JSON.parse(payload)\n    return !Array.isArray(v) && typeof v === 'object' && v !== null\n      && (typeof (v as any).message === 'string' || typeof (v as any).error === 'string')\n  } catch { return false }\n}","typeGuard":"function isGitLabErrorEnvelope(parsed: unknown): parsed is { message?: string; error?: string } {\n  return typeof parsed === 'object' && parsed !== null\n    && (typeof (parsed as any).message === 'string' || typeof (parsed as any).error === 'string')\n}","tryCatchPattern":"try {\n  const list = parseGlabJsonList<MrType>(payload)\n  // use list\n} catch (error) {\n  if (error instanceof GlabNonListResponseError) { /* opaque, surface raw */ }\n  else if (error instanceof Error && error.message.startsWith('GitLab returned an error:')) {\n    // auth/scope/not-found — read message, refresh token or fix path\n  } else throw error\n}","preventionTips":["Keep GitLab tokens fresh and scoped correctly for the operations you perform.","Validate the endpoint path matches the GitLab instance's API version.","Test glab commands against the target instance in isolation to distinguish provider errors from proxy errors."],"tags":["gitlab","glab","api","auth","error-envelope"],"backgroundTag":null,"analyzedSha":"1136503c6a231a16dce8f921f6fadb63d181e8db","analyzedAt":"2026-08-12T23:15:58.167Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}