{"record":{"id":"8a35841fa78fa7ba","repo":"JuliusBrussee/caveman","slug":"sort-must-be-an-object","errorCode":null,"errorMessage":"sort must be an object","messagePattern":"sort must be an object","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/cli/src/agent-mcp.ts","lineNumber":521,"sourceCode":"    throw new Error(`${key} must be one of ${values.join(\", \")}`);\n  }\n}\n\nfunction validateTraceSearch(args: JSONObject): void {\n  validateTraceFilters(args);\n  for (const key of [\"from\", \"to\"]) {\n    const value = args[key];\n    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> {","sourceCodeStart":503,"sourceCodeEnd":539,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/cli/src/agent-mcp.ts#L503-L539","documentation":"The optional `sort` argument of trace_search must be a plain object with at most `by` and `dir` keys. If sort is provided but is not an object (number, string, boolean, or null) or is an array, this error is thrown before any key checks.","triggerScenarios":"trace_search with {\"sort\":\"timestamp\"} (string instead of object), {\"sort\":[\"timestamp\"]} (array), {\"sort\":null} (non-undefined null), or {\"sort\":true}.","commonSituations":"Shorthand sort strings copied from REST APIs that accept \"field:dir\"; arrays of sort clauses from Elasticsearch-style clients; JSON configs where sort was a string; null sent explicitly by serializers.","solutions":["Wrap the sort spec in an object: {\"sort\":{\"by\":\"timestamp\",\"dir\":\"desc\"}}.","Don't send null — omit the key entirely when not sorting.","Convert array/string sort forms into the object form.","Ensure the object has only `by` and `dir` keys (see the unknown-keys error)."],"exampleFix":"// before\n{ \"sort\": \"timestamp:desc\" }\n// after\n{ \"sort\": { \"by\": \"timestamp\", \"dir\": \"desc\" } }","handlingStrategy":"type-guard","validationCode":"if (args.sort !== undefined && (typeof args.sort !== \"object\" || args.sort === null || Array.isArray(args.sort)))\n  throw new Error(\"sort must be an object like {by, dir}\");","typeGuard":"function isPlainObject(v) {\n  return typeof v === \"object\" && v !== null && !Array.isArray(v);\n}","tryCatchPattern":"try {\n  await traceSearch(args);\n} catch (e) {\n  if (e.message === \"sort must be an object\") {\n    throw new Error(\"Use sort: { by: 'timestamp', dir: 'desc' } — string/array sort is not supported\");\n  }\n  throw e;\n}","preventionTips":["Always build sort as {by, dir}; never send shorthand strings or arrays.","Omit sort (don't send null) when no ordering is needed.","Centralize sort construction in one helper."],"tags":["validation","type-mismatch","mcp","sorting"],"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"}