{"record":{"id":"f593f56f1ecfe2f9","repo":"mastra-ai/mastra","slug":"invalid-scoring-filter-path-s-invalid-map-p","errorCode":null,"errorMessage":"Invalid scoring filter: path(s) ${invalid.map(p => `\"${p}\"`).join(', ')} must start with one of: ${SCORING_PREDICATE_ROOTS.join(', ')}","messagePattern":"Invalid scoring filter: path\\(s\\) (.+?)\"`\\)\\.join\\(', '\\)\\} must start with one of: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/core/src/evals/predicate.ts","lineNumber":113,"sourceCode":"/**\n * Evaluate a scoring filter against a scoring context. Never throws for path\n * resolution failures — missing paths propagate to `false` on comparison ops\n * and `in` (fail closed: an unresolvable filter does not score), but to\n * `true` on the negated ops `notIn` and `notExists` (a missing value is\n * trivially \"not in\" any set), and to `false` on `exists`.\n */\nexport const evaluateScoringPredicate: (pred: Predicate, ctx: ScoringPredicateContext) => boolean =\n  createPredicateEvaluator(resolveScoringPath);\n\n/**\n * Validate a scoring filter at definition time. Throws if any referenced path\n * doesn't start with a known scoring root, so typos fail loud when the\n * binding is registered instead of silently skipping all scoring at runtime.\n */\nexport function validateScoringPredicate(pred: Predicate): void {\n  const invalid = collectInvalidPredicatePaths(pred, SCORING_PREDICATE_ROOTS);\n  if (invalid.length > 0) {\n    throw new Error(\n      `Invalid scoring filter: path(s) ${invalid.map(p => `\"${p}\"`).join(', ')} must start with one of: ${SCORING_PREDICATE_ROOTS.join(', ')}`,\n    );\n  }\n}\n","sourceCodeStart":95,"sourceCodeEnd":118,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/packages/core/src/evals/predicate.ts#L95-L118","documentation":"Scoring filters (predicates on scorer bindings) may only reference paths rooted at requestContext, entity, entityType, source, threadId, resourceId, or projectId. `validateScoringPredicate` walks every path in the predicate and throws this Error at definition time (constructor / listScorers) if any path starts with a disallowed root — chiefly `input`/`output`, which are deliberately excluded because output-dependent filters cannot be evaluated against stored records at query time. This fails loud so filters don't silently skip all scoring at runtime.","triggerScenarios":"Registering a scorer binding whose `filter` predicate references `input.x`, `output.y`, `run.foo`, or any typo'd root — thrown from the scorer-binding constructor or `mastra.listScorers()`.","commonSituations":"Writing a filter like `output.score > 0.5` hoping to conditionally score on results; typos such as `thread.id` instead of `threadId`; assuming request context fields are top-level instead of under `requestContext.`; upgrading from a version where output-rooted filters were tolerated.","solutions":["Rewrite each invalid path to start with one of: requestContext, entity, entityType, source, threadId, resourceId, projectId","Move input-dependent conditioning out of the filter (e.g. use sampling plus runtime scoring logic instead)","Fix typos to the exact scalar roots (e.g. `threadId` not `thread.id`)","For nested request data, use `requestContext.<key>` with dot-joined flat keys (e.g. `requestContext.user.tier`)"],"exampleFix":"// before\nnew ScorerBinding({\n  scorer: toneScorer,\n  filter: { path: 'output.score', gt: 0.5 }, // invalid root\n});\n// after\nnew ScorerBinding({\n  scorer: toneScorer,\n  filter: { path: 'requestContext.user.tier', eq: 'pro' },\n});","handlingStrategy":"validation","validationCode":"const ROOTS = ['requestContext','entity','entityType','source','threadId','resourceId','projectId'];\nexport function checkScoringFilter(pred) {\n  const paths = collectPaths(pred); // walk and/or/comparison nodes for .path\n  const bad = paths.filter(p => !ROOTS.some(r => p === r || p.startsWith(r + '.')));\n  if (bad.length) throw new Error(`Filter paths not allowed: ${bad.join(', ')}`);\n}\ncheckScoringFilter(binding.filter); // before constructing the binding","typeGuard":null,"tryCatchPattern":"try {\n  const binding = new ScorerBinding({ scorer, filter });\n} catch (e) {\n  if (e.message.startsWith('Invalid scoring filter:')) {\n    console.error('Rewrite filter paths to one of: requestContext, entity, entityType, source, threadId, resourceId, projectId');\n  } else throw e;\n}","preventionTips":["Only reference the seven allowed roots in filters; never filter on input/output","Use requestContext.<flat.key> for request-scoped fields","Run filter validation in CI against all scorer binding definitions","Beware typos between scalar roots (threadId) and nested names (thread.id)"],"tags":["scorer","predicate","validation","configuration"],"backgroundTag":"invalid-predicate-path","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}