{"record":{"id":"e5f3b2a5cffd6f73","repo":"JuliusBrussee/caveman","slug":"filters-must-be-an-object","errorCode":null,"errorMessage":"filters must be an object","messagePattern":"filters must be an object","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"packages/cli/src/agent-mcp.ts","lineNumber":448,"sourceCode":"  if (typeof candidate.status === \"number\") structuredContent.status = candidate.status;\n  return {\n    content: [{ type: \"text\", text: JSON.stringify(structuredContent) }],\n    structuredContent,\n    isError: true,\n  };\n}\n\nasync function withProjectQuery(client: AgentMcpClient, path: string): Promise<string> {\n  if (!PROJECT_QUERY_PATHS.has(path)) return path;\n  const projectId = await client.projectId();\n  const query = new URLSearchParams({ project_id: projectId });\n  return `${path}?${query}`;\n}\n\nfunction validateTraceFilters(args: JSONObject): void {\n  const filters = args.filters;\n  if (filters === undefined) return;\n  if (!filters || typeof filters !== \"object\" || Array.isArray(filters)) throw new Error(\"filters must be an object\");\n  const unknown = Object.keys(filters).filter((key) => !TRACE_FILTER_KEYS.has(key));\n  if (unknown.length > 0) throw new Error(`unknown trace filter(s): ${unknown.sort().join(\", \")}`);\n  const stringListKeys = [\n    \"workflow\",\n    \"agent\",\n    \"model\",\n    \"provider\",\n    \"error_code\",\n    \"auth_mode\",\n    \"runtime_mode\",\n    \"cache_status\",\n    \"session_id\",\n    \"client_user_hash\",\n    \"trace_id\",\n    \"member_user_id\",\n    \"api_key_id\",\n    \"optimization_id\",\n    \"status_class\",","sourceCodeStart":430,"sourceCodeEnd":466,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/packages/cli/src/agent-mcp.ts#L430-L466","documentation":"validateTraceFilters() rejects a `filters` argument that is present but not a plain object: null, an array, a string, or a number all fail. `filters` must be a JSON object whose keys are recognized trace filter names; omitting it entirely is fine.","triggerScenarios":"Passing filters as an array of pairs ([[\"model\", [...]]]), a JSON string ('{\"model\":[\"gpt\"]}'), or null in a caveman_trace_search call.","commonSituations":"A model double-serializing the filters object; clients building query-string-style arrays; treating filters as optional and sending null instead of omitting the key.","solutions":["Pass filters as a real JSON object: {\"filters\": {\"model\": [\"gpt-4\"]}}.","Omit the filters key entirely when no filtering is needed.","JSON.parse strings before embedding them in the tool args."],"exampleFix":"// before\n{ filters: JSON.stringify({ model: [\"gpt-4\"] }) }\n\n// after\n{ filters: { model: [\"gpt-4\"] } }","handlingStrategy":"type-guard","validationCode":"if (args.filters !== undefined) {\n  if (typeof args.filters !== \"object\" || args.filters === null || Array.isArray(args.filters)) {\n    throw new Error(\"filters must be a plain object\");\n  }\n}","typeGuard":"function isPlainObject(v: unknown): v is Record<string, unknown> {\n  return typeof v === \"object\" && v !== null && !Array.isArray(v);\n}","tryCatchPattern":null,"preventionTips":["Build filters objects in code, never from serialized strings.","Omit filters rather than sending null when unneeded."],"tags":["mcp","validation","filters","traces"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}