{"record":{"id":"023c67b1bcadb5a6","repo":"paperclipai/paperclip","slug":"request-failed-response-status","errorCode":null,"errorMessage":"Request failed: ${response.status}","messagePattern":"Request failed: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"cli/src/client/board-auth.ts","lineNumber":171,"sourceCode":"  if (init?.body !== undefined && !headers.has(\"content-type\")) {\n    headers.set(\"content-type\", \"application/json\");\n  }\n  if (!headers.has(\"accept\")) {\n    headers.set(\"accept\", \"application/json\");\n  }\n\n  const response = await fetch(url, {\n    ...init,\n    headers,\n  });\n\n  if (!response.ok) {\n    const body = await response.json().catch(() => null);\n    const message =\n      body && typeof body === \"object\" && typeof (body as { error?: unknown }).error === \"string\"\n        ? (body as { error: string }).error\n        : `Request failed: ${response.status}`;\n    throw new Error(message);\n  }\n\n  return response.json() as Promise<T>;\n}\n\nexport async function openUrl(url: string): Promise<boolean> {\n  const { command, args } =\n    process.platform === \"darwin\"\n      ? { command: \"open\", args: [url] }\n      : process.platform === \"win32\"\n        ? { command: \"cmd\", args: [\"/c\", \"start\", \"\", url] }\n        : { command: \"xdg-open\", args: [url] };\n\n  return new Promise<boolean>((resolve) => {\n    let child: ChildProcess;\n    try {\n      child = spawn(command, args, { detached: true, stdio: \"ignore\" });\n    } catch {","sourceCodeStart":153,"sourceCodeEnd":189,"githubUrl":"https://github.com/paperclipai/paperclip/blob/67001ec6eb96ae601aa27bc91d9b2415d665334a/cli/src/client/board-auth.ts#L153-L189","documentation":"Generic fallback thrown by the internal requestJson() helper in cli/src/client/board-auth.ts when an HTTP response is non-OK (not 2xx) AND the response body is missing, unparseable, or has no string-shaped `error` field. The literal status code is interpolated so the caller sees what the server returned. This helper backs the CLI board-auth flow (create-challenge, poll, /me, revoke), so the failure surfaces during CLI login/token-refresh/revoke, not during normal agent API calls.","triggerScenarios":"A board-auth endpoint returns a non-2xx (e.g. 401 on /api/cli-auth/me with a bad/revoked boardApiToken, 404 on a challenge pollPath that does not exist, 500 from the server, or a network proxy returning 502/503) and the body is either empty, not JSON, or JSON without `{ error: string }`. Also triggered if the apiBase is wrong (points at a non-Paperclip host returning HTML, so `.json().catch(()=>null)` yields null).","commonSituations":"Wrong --api-base (typo, trailing path, pointing at the UI host instead of the API host), server down or restarting during `paperclipai login`, reverse proxy returning an HTML error page (status body not JSON), token revoked between challenge approval and the /api/cli-auth/me call, or a version mismatch where the server's cli-auth routes changed.","solutions":["Re-run with the correct API base: `paperclipai login --api-base https://your-paperclip.example.com` (no trailing path).","Check the server is up and the cli-auth routes exist: `curl -i <apiBase>/api/health`.","If a proxy is in front, confirm it passes JSON bodies through and does not rewrite 4xx/5xx into HTML pages.","Retry the login; a transient 5xx during deploy resolves once the server is healthy."],"exampleFix":"// before\npaperclipai login --api-base https://corp.example.com/paperclip/api\n// (HTML 404 page → \"Request failed: 404\")\n\n// after — point at the API root, no /api suffix\npaperclipai login --api-base https://corp.example.com","handlingStrategy":"try-catch","validationCode":"// Before calling loginBoardCli / requestJson-style helpers, sanity-check the apiBase.\nfunction assertApiBase(apiBase: string): void {\n  const u = new URL(apiBase); // throws on garbage\n  if (!u.protocol.startsWith('http')) throw new Error(`apiBase must be http(s): ${apiBase}`);\n  if (/[?]#/.test(u.href)) throw new Error(`apiBase must not include query/fragment: ${apiBase}`);\n}","typeGuard":"function isApiErrorBody(body: unknown): body is { error: string } {\n  return typeof body === 'object' && body !== null &&\n    typeof (body as any).error === 'string';\n}","tryCatchPattern":"try {\n  await loginBoardCli({ apiBase, ... });\n} catch (err) {\n  const msg = err instanceof Error ? err.message : String(err);\n  if (/^Request failed: \\d+$/.test(msg)) {\n    const status = Number(msg.slice('Request failed: '.length));\n    console.error(`Board auth HTTP ${status} from ${apiBase}; check server health and apiBase.`);\n  }\n  throw err;\n}","preventionTips":["Always normalize apiBase (trim, strip trailing slashes) before passing to loginBoardCli.","Pre-flight GET <apiBase>/api/health before starting an interactive login.","Do not point the CLI at a proxy that rewrites error responses to HTML.","Keep CLI and server versions aligned so cli-auth route paths match."],"tags":["network","auth","cli","http-status","board-auth"],"backgroundTag":null,"analyzedSha":"67001ec6eb96ae601aa27bc91d9b2415d665334a","analyzedAt":"2026-08-12T12:05:45.408Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}