JuliusBrussee/caveman · error
cursor.sort_value is required
Error message
cursor.sort_value is required
What it means
validateTraceSearch enforces the shape of an optional pagination cursor passed to the trace-search MCP tool. A cursor, if provided, must be an object whose sort_value is a non-empty string; sort_value is the mandatory key the server uses to resume iteration. This error means a cursor object was supplied without a valid sort_value.
Solutions
- Set cursor.sort_value to the non-empty string returned by the previous trace-search page
- If you have no cursor yet, omit the cursor argument entirely instead of passing {}
- Ensure sort_value is a string, not a number or null
- Only include the allowed keys sort_value, request_id, group_key
Example fix
// before
await callTool(client, "caveman_trace_search", { query: "err", cursor: { request_id: "r1" } });
// after
await callTool(client, "caveman_trace_search", { query: "err", cursor: { sort_value: "2026-09-20T00:00:00Z", request_id: "r1" } }); Defensive patterns
Strategy: validation
Validate before calling
function isValidCursor(c) { return c === undefined || (typeof c === "object" && c !== null && !Array.isArray(c) && typeof c.sort_value === "string" && c.sort_value !== ""); } Type guard
function isCursor(c): c is { sort_value: string; request_id?: string; group_key?: string } { return typeof c === "object" && c !== null && !Array.isArray(c) && typeof (c as any).sort_value === "string" && (c as any).sort_value !== ""; } Try / catch
try { await callTool(client, "caveman_trace_search", args); } catch (e) { if (String(e.message).includes("cursor.sort_value is required")) { delete args.cursor; return callTool(client, "caveman_trace_search", args); } throw e; } Prevention
- Store the full cursor object from the previous page verbatim instead of rebuilding it
- Only pass a cursor when resuming pagination; start with no cursor
- Never hand-construct cursor objects from partial data
- Type the cursor in your client code so sort_value is required
When it happens
Trigger: Calling the trace search tool with args.cursor set to an object like {} or {sort_value:""} or {sort_value:123} or {request_id:"r1"} only (sort_value absent).
Common situations: Agents constructing a cursor by hand from a truncated previous response; copying a cursor but dropping its sort_value; JSON round-trips turning sort_value into a non-string; clients caching an empty cursor object after the last page.
Understand the failure class
Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.
Related errors
- cursor has unknown key(s)
- cursor. must be a string
- cursor must be an object
- page_size must be an integer from 1 to 500
- apiKey, baseURL, and agent are required
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/e93067f0cdff2b79.
Report an issue: GitHub.
Appendix: source
Thrown at packages/cli/src/agent-mcp.ts:532
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": {
const [identity, projects, selectedProjectId] = await Promise.all([View on GitHub (pinned to 3ee70a1026)