{"record":{"id":"f6909eabce4b15fc","repo":"JuliusBrussee/caveman","slug":"cave-auth-required","errorCode":"cave_auth_required","errorMessage":"Not logged in. Run `caveman login` or set CAVE_TOKEN for headless use.","messagePattern":"Not logged in\\. Run `caveman login` or set CAVE_TOKEN for headless use\\.","errorType":"error_code","errorClass":"AgentMcpHTTPError","httpStatus":401,"severity":"error","filePath":"packages/cli/src/index.ts","lineNumber":18372,"sourceCode":"    this.name = \"AgentMcpHTTPError\";\n    this.code = code;\n    this.status = status;\n  }\n}\n\nfunction agentMcpTimeoutMS(): number {\n  const raw = process.env.CAVE_AGENT_TOOL_TIMEOUT_MS ?? \"30000\";\n  const value = Number(raw);\n  return Number.isSafeInteger(value) && value >= 100 && value <= 120_000 ? value : 30_000;\n}\n\nasync function agentMcpRequest(\n  path: string,\n  options: { method?: \"GET\" | \"POST\"; body?: JSONObject } = {},\n): Promise<JSONValue> {\n  const cfg = await config();\n  if (!cfg.token) {\n    throw new AgentMcpHTTPError(\n      \"Not logged in. Run `caveman login` or set CAVE_TOKEN for headless use.\",\n      \"cave_auth_required\",\n      401,\n    );\n  }\n  let response: Response;\n  try {\n    const request: RequestInit = {\n      method: options.method ?? \"GET\",\n      signal: AbortSignal.timeout(agentMcpTimeoutMS()),\n      headers: {\n        authorization: `Bearer ${cfg.token}`,\n        ...(options.method === \"POST\"\n          ? { \"content-type\": \"application/json\", \"x-cave-csrf\": \"cli\" }\n          : {}),\n      },\n    };\n    if (options.method === \"POST\") request.body = JSON.stringify(options.body ?? {});","sourceCodeStart":18354,"sourceCodeEnd":18390,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/cli/src/index.ts#L18354-L18390","documentation":"agentMcpRequest() performs authenticated API calls against the Cave backend. When the local config has no stored token, it throws an AgentMcpHTTPError with code `cave_auth_required` (HTTP 401) telling the user to run `caveman login` or set CAVE_TOKEN.","triggerScenarios":"Any CLI/agent command that reaches the MCP HTTP layer while `cfg.token` is empty: fresh install, CI container without login, expired/removed credentials, or CAVE_TOKEN unset in the environment.","commonSituations":"Running in a headless CI job where interactive `caveman login` is impossible, switching machines and forgetting to log in, or a config reset that wiped the stored token.","solutions":["Run `caveman login` to store a token","Set the CAVE_TOKEN environment variable in the shell/CI environment","Verify the config file still contains the token (`caveman whoami` or inspect config)","Re-authenticate if the token was revoked or expired"],"exampleFix":"// before (CI step)\n- run: caveman agent sync\n// after\n- run: |\n    export CAVE_TOKEN=${{ secrets.CAVE_TOKEN }}\n    caveman agent sync","handlingStrategy":"try-catch","validationCode":"const cfg = await config();\nif (!cfg.token && !process.env.CAVE_TOKEN) { console.error('Not authenticated: run `caveman login` or set CAVE_TOKEN'); process.exit(1); }","typeGuard":"const isAuthError = (e: unknown): e is AgentMcpHTTPError =>\n  e instanceof AgentMcpHTTPError && e.code === 'cave_auth_required';","tryCatchPattern":"try { return await agentMcpRequest(path, opts); } catch (e) {\n  if (isAuthError(e)) { console.error('Run `caveman login` or set CAVE_TOKEN.'); process.exit(1); }\n  throw e;\n}","preventionTips":["Run `caveman login` once per machine before scripted use","Inject CAVE_TOKEN from a secret store in CI","Check auth with a cheap command (whoami/status) before long workflows","Alert on config resets that drop the stored token"],"tags":["auth","cli","http","headless"],"backgroundTag":"authentication-required","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}