JuliusBrussee/caveman · error

cursor must be an object

Error message

cursor must be an object

What it means

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.

Solutions

  1. Pass the cursor object exactly as returned by the previous page's response (with sort_value/request_id/group_key).
  2. Omit `cursor` entirely for the first page instead of sending null.
  3. If you have a string token from elsewhere, it won't work here — construct the object form {"sort_value":"..."}.
  4. Ensure the cursor object is not an array and its keys are only sort_value, request_id, group_key.

Example fix

// before
{ "cursor": "abc123token" }
// after
{ "cursor": { "sort_value": "2025-01-01T00:00:00Z", "request_id": "req_1" } }
Defensive patterns

Strategy: validation

Validate before calling

if (args.cursor !== undefined && (typeof args.cursor !== "object" || args.cursor === null || Array.isArray(args.cursor)))
  throw new Error("cursor must be the object returned by the previous page, not a string token");

Type guard

function isCursorObject(c) {
  return typeof c === "object" && c !== null && !Array.isArray(c) && typeof c.sort_value === "string";
}

Try / catch

try {
  await traceSearch(args);
} catch (e) {
  if (e.message === "cursor must be an object") {
    throw new Error("Pass the cursor object from the previous response, or omit cursor for page 1");
  }
  throw e;
}

Prevention

When it happens

Trigger: trace_search with {"cursor":"eyJvZmZzZXQi...} (opaque token string from another API), {"cursor":0}, {"cursor":[...]}, or {"cursor":null} instead of omitting it.

Common situations: 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.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/c0f362784990e71e. Report an issue: GitHub.

Appendix: source

Thrown at packages/cli/src/agent-mcp.ts:529

    if (value !== undefined && typeof value !== "string") throw new Error(`${key} must be a string`);
  }
  optionalEnum(args, "date_field", ["occurred", "updated"]);
  optionalEnum(args, "group_by", ["session", "workflow", "model", "member"]);
  const pageSize = args.page_size;
  if (pageSize !== undefined && (!Number.isSafeInteger(pageSize) || (pageSize as number) < 1 || (pageSize as number) > 500)) {
    throw new Error("page_size must be an integer from 1 to 500");
  }
  const sort = args.sort;
  if (sort !== undefined) {
    if (!sort || typeof sort !== "object" || Array.isArray(sort)) throw new Error("sort must be an object");
    const unknown = Object.keys(sort).filter((key) => key !== "by" && key !== "dir");
    if (unknown.length > 0) throw new Error(`sort has unknown key(s): ${unknown.sort().join(", ")}`);
    optionalEnum(sort, "by", ["timestamp", "total_cost_usd", "latency_ms", "total_tokens"]);
    optionalEnum(sort, "dir", ["asc", "desc"]);
  }
  const cursor = args.cursor;
  if (cursor !== undefined) {
    if (!cursor || typeof cursor !== "object" || Array.isArray(cursor)) throw new Error("cursor must be an object");
    const unknown = Object.keys(cursor).filter((key) => key !== "sort_value" && key !== "request_id" && key !== "group_key");
    if (unknown.length > 0) throw new Error(`cursor has unknown key(s): ${unknown.sort().join(", ")}`);
    if (typeof cursor.sort_value !== "string" || cursor.sort_value === "") throw new Error("cursor.sort_value is required");
    for (const key of ["request_id", "group_key"]) {
      if (cursor[key] !== undefined && typeof cursor[key] !== "string") throw new Error(`cursor.${key} must be a string`);
    }
  }
}

async function callTool(client: AgentMcpClient, name: string, rawArgs: unknown): Promise<ToolResult> {
  const started = Date.now();
  let outcome: "ok" | "error" = "ok";
  try {
    const args = objectArg(rawArgs);
    const allowed = TOOL_ARGUMENT_KEYS[name];
    const unknown = Object.keys(args).filter((key) => !allowed?.has(key));
    if (unknown.length > 0) throw new Error(`unknown argument(s): ${unknown.sort().join(", ")}`);
    let value: JSONValue;

View on GitHub (pinned to 3ee70a1026)