JuliusBrussee/caveman · error

cursor has unknown key(s)

Error message

cursor has unknown key(s): ${unknown.sort().join(", ")}

What it means

A `cursor` object supplied to trace_search may contain only `sort_value` (required string), and optionally `request_id` and `group_key` (strings). Any additional key triggers this error listing the disallowed keys sorted alphabetically.

Solutions

  1. Remove all keys from the cursor except sort_value, request_id, and group_key.
  2. Echo back only the exact cursor object the API returned on the previous page.
  3. Verify required cursor.sort_value is a non-empty string (see related error).
  4. Regenerate the cursor from a fresh page response instead of hand-editing old ones.

Example fix

// before
{ "cursor": { "sort_value": "2025-01-01T00:00:00Z", "offset": 40 } }
// after
{ "cursor": { "sort_value": "2025-01-01T00:00:00Z" } }
Defensive patterns

Strategy: validation

Validate before calling

const CURSOR_KEYS = new Set(["sort_value", "request_id", "group_key"]);
if (args.cursor && Object.keys(args.cursor).some(k => !CURSOR_KEYS.has(k)))
  throw new Error("cursor may only contain sort_value, request_id, and group_key");

Type guard

function isValidCursor(c) {
  return isPlainObject(c) && Object.keys(c).every(k => CURSOR_KEYS.has(k));
}

Try / catch

try {
  await traceSearch(args);
} catch (e) {
  if (e.message.startsWith("cursor has unknown key")) {
    const { sort_value, request_id, group_key } = args.cursor;
    args.cursor = { sort_value, request_id, group_key };
    return traceSearch(args);
  }
  throw e;
}

Prevention

When it happens

Trigger: trace_search with {"cursor":{"sort_value":"...","offset":40}}, extra fields like {"page":2} inside cursor, or stale cursors from a prior schema version carrying now-removed keys.

Common situations: Manually assembling cursors and adding convenience fields; older clients persisting cursor JSON with deprecated keys; copying the response object including envelope metadata (e.g. next_cursor itself) into the cursor field.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


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

Appendix: source

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

  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;
    switch (name) {
      case "caveman_context": {

View on GitHub (pinned to 3ee70a1026)