{"record":{"id":"2c3dc1701ee7c69b","repo":"JuliusBrussee/caveman","slug":"key-must-be-a-string","errorCode":null,"errorMessage":"${key} must be a string","messagePattern":"(.+?) must be a string","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/cli/src/agent-mcp.ts","lineNumber":511,"sourceCode":"    if (typeof monitor.id !== \"string\" || monitor.id.trim() === \"\") throw new Error(\"filter monitor.id is required\");\n    if (typeof monitor.verdict !== \"string\" || ![\"pass\", \"fail\", \"error\"].includes(monitor.verdict)) {\n      throw new Error(\"filter monitor.verdict must be pass, fail, or error\");\n    }\n  }\n}\n\nfunction optionalEnum(args: JSONObject, key: string, values: string[]): void {\n  const value = args[key];\n  if (value !== undefined && (typeof value !== \"string\" || !values.includes(value))) {\n    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\");","sourceCodeStart":493,"sourceCodeEnd":529,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/cli/src/agent-mcp.ts#L493-L529","documentation":"In validateTraceSearch, the optional range bounds `from` and `to` must be strings (date/timestamp strings). Passing numbers, objects, arrays, or booleans for either throws this error naming the offending key.","triggerScenarios":"trace_search with {\"from\":1700000000000} (epoch number instead of ISO string), {\"to\":{\"$date\":\"...\"}} (object), or {\"from\":true} — any defined non-string value for from/to.","commonSituations":"Passing JS Date objects or epoch millis from client code that forgot to serialize; JSON configs where dates were numbers; mixing this API's string-date convention with another API that accepts numbers.","solutions":["Convert dates to strings before calling, e.g. new Date(x).toISOString() for from/to.","Use ISO 8601 formatted strings (\"2026-01-01T00:00:00Z\").","Remove from/to if unbounded queries are acceptable (both optional).","Check the API's expected date format documented for the tool."],"exampleFix":"// before\n{ \"from\": 1735689600000, \"to\": 1738368000000 }\n// after\n{ \"from\": \"2025-01-01T00:00:00Z\", \"to\": \"2025-02-01T00:00:00Z\" }","handlingStrategy":"type-guard","validationCode":"for (const k of [\"from\", \"to\"]) {\n  if (args[k] !== undefined && typeof args[k] !== \"string\")\n    args[k] = new Date(args[k]).toISOString();\n}","typeGuard":"function isDateString(v) {\n  return typeof v === \"string\" && !Number.isNaN(Date.parse(v));\n}","tryCatchPattern":"try {\n  await traceSearch(args);\n} catch (e) {\n  if (/^(from|to) must be a string$/.test(e.message)) {\n    const key = e.message.split(\" \")[0];\n    args[key] = new Date(args[key]).toISOString();\n    return traceSearch(args);\n  }\n  throw e;\n}","preventionTips":["Serialize all dates to ISO strings at the client boundary.","Never pass Date objects or epoch numbers into JSON tool arguments.","Add a pre-send serializer for date-like fields."],"tags":["validation","type-mismatch","mcp","dates"],"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"}