{"record":{"id":"c0f362784990e71e","repo":"JuliusBrussee/caveman","slug":"cursor-must-be-an-object","errorCode":null,"errorMessage":"cursor must be an object","messagePattern":"cursor must be an object","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/cli/src/agent-mcp.ts","lineNumber":529,"sourceCode":"    if (value !== undefined && typeof value !== \"string\") throw new Error(`${key} must be a string`);\n  }\n  optionalEnum(args, \"date_field\", [\"occurred\", \"updated\"]);\n  optionalEnum(args, \"group_by\", [\"session\", \"workflow\", \"model\", \"member\"]);\n  const pageSize = args.page_size;\n  if (pageSize !== undefined && (!Number.isSafeInteger(pageSize) || (pageSize as number) < 1 || (pageSize as number) > 500)) {\n    throw new Error(\"page_size must be an integer from 1 to 500\");\n  }\n  const sort = args.sort;\n  if (sort !== undefined) {\n    if (!sort || typeof sort !== \"object\" || Array.isArray(sort)) throw new Error(\"sort must be an object\");\n    const unknown = Object.keys(sort).filter((key) => key !== \"by\" && key !== \"dir\");\n    if (unknown.length > 0) throw new Error(`sort has unknown key(s): ${unknown.sort().join(\", \")}`);\n    optionalEnum(sort, \"by\", [\"timestamp\", \"total_cost_usd\", \"latency_ms\", \"total_tokens\"]);\n    optionalEnum(sort, \"dir\", [\"asc\", \"desc\"]);\n  }\n  const cursor = args.cursor;\n  if (cursor !== undefined) {\n    if (!cursor || typeof cursor !== \"object\" || Array.isArray(cursor)) throw new Error(\"cursor must be an object\");\n    const unknown = Object.keys(cursor).filter((key) => key !== \"sort_value\" && key !== \"request_id\" && key !== \"group_key\");\n    if (unknown.length > 0) throw new Error(`cursor has unknown key(s): ${unknown.sort().join(\", \")}`);\n    if (typeof cursor.sort_value !== \"string\" || cursor.sort_value === \"\") throw new Error(\"cursor.sort_value is required\");\n    for (const key of [\"request_id\", \"group_key\"]) {\n      if (cursor[key] !== undefined && typeof cursor[key] !== \"string\") throw new Error(`cursor.${key} must be a string`);\n    }\n  }\n}\n\nasync function callTool(client: AgentMcpClient, name: string, rawArgs: unknown): Promise<ToolResult> {\n  const started = Date.now();\n  let outcome: \"ok\" | \"error\" = \"ok\";\n  try {\n    const args = objectArg(rawArgs);\n    const allowed = TOOL_ARGUMENT_KEYS[name];\n    const unknown = Object.keys(args).filter((key) => !allowed?.has(key));\n    if (unknown.length > 0) throw new Error(`unknown argument(s): ${unknown.sort().join(\", \")}`);\n    let value: JSONValue;","sourceCodeStart":511,"sourceCodeEnd":547,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/cli/src/agent-mcp.ts#L511-L547","documentation":"The optional `cursor` argument for trace_search pagination must be a plain object. If cursor is supplied as a non-object (string token, number, null, array), this error is thrown before the object's keys are inspected.","triggerScenarios":"trace_search with {\"cursor\":\"eyJvZmZzZXQi...} (opaque token string from another API), {\"cursor\":0}, {\"cursor\":[...]}, or {\"cursor\":null} instead of omitting it.","commonSituations":"Reusing cursor tokens from a different paginated API that uses string cursors; JSON round-trips that turned the object into a string; client code that always initializes cursor to null and sends it.","solutions":["Pass the cursor object exactly as returned by the previous page's response (with sort_value/request_id/group_key).","Omit `cursor` entirely for the first page instead of sending null.","If you have a string token from elsewhere, it won't work here — construct the object form {\"sort_value\":\"...\"}.","Ensure the cursor object is not an array and its keys are only sort_value, request_id, group_key."],"exampleFix":"// before\n{ \"cursor\": \"abc123token\" }\n// after\n{ \"cursor\": { \"sort_value\": \"2025-01-01T00:00:00Z\", \"request_id\": \"req_1\" } }","handlingStrategy":"validation","validationCode":"if (args.cursor !== undefined && (typeof args.cursor !== \"object\" || args.cursor === null || Array.isArray(args.cursor)))\n  throw new Error(\"cursor must be the object returned by the previous page, not a string token\");","typeGuard":"function isCursorObject(c) {\n  return typeof c === \"object\" && c !== null && !Array.isArray(c) && typeof c.sort_value === \"string\";\n}","tryCatchPattern":"try {\n  await traceSearch(args);\n} catch (e) {\n  if (e.message === \"cursor must be an object\") {\n    throw new Error(\"Pass the cursor object from the previous response, or omit cursor for page 1\");\n  }\n  throw e;\n}","preventionTips":["Echo the previous response's cursor object verbatim; don't stringify it.","Initialize cursor to undefined, not null, in client state.","Don't reuse cursor tokens from other paginated APIs."],"tags":["validation","pagination","mcp","type-mismatch"],"backgroundTag":"type-mismatch","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"}