{"record":{"id":"cb66d08349c9a300","repo":"mastra-ai/mastra","slug":"thread-knowledge-scope-requires-resource-and-org-a","errorCode":null,"errorMessage":"Thread knowledge scope requires resource and org ancestors","messagePattern":"Thread knowledge scope requires resource and org ancestors","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/core/src/storage/domains/knowledge/base.ts","lineNumber":295,"sourceCode":"  const entriesByLevel = new Map<KnowledgeScopeLevel, string>();\n  for (const entry of scope) {\n    const separator = entry.indexOf(':');\n    const level = entry.slice(0, separator) as KnowledgeScopeLevel;\n    const id = entry.slice(separator + 1);\n    if (separator <= 0 || !id || id.includes('\\u001f') || SCOPE_ORDER[level] === undefined) {\n      throw new Error(`Invalid knowledge scope entry: ${entry}`);\n    }\n    const existing = entriesByLevel.get(level);\n    if (existing && existing !== entry) {\n      throw new Error(`Knowledge scope contains multiple ${level} entries`);\n    }\n    entriesByLevel.set(level, entry);\n  }\n  if (entriesByLevel.size === 0) {\n    throw new Error('Knowledge scope cannot be empty');\n  }\n  if (entriesByLevel.has('thread') && (!entriesByLevel.has('resource') || !entriesByLevel.has('org'))) {\n    throw new Error('Thread knowledge scope requires resource and org ancestors');\n  }\n  if (entriesByLevel.has('resource') && !entriesByLevel.has('org')) {\n    throw new Error('Resource knowledge scope requires an org ancestor');\n  }\n\n  const unique = [...new Set(scope)];\n  unique.sort((a, b) => {\n    const aLevel = a.slice(0, a.indexOf(':')) as KnowledgeScopeLevel;\n    const bLevel = b.slice(0, b.indexOf(':')) as KnowledgeScopeLevel;\n    const aOrder = SCOPE_ORDER[aLevel] ?? Number.MAX_SAFE_INTEGER;\n    const bOrder = SCOPE_ORDER[bLevel] ?? Number.MAX_SAFE_INTEGER;\n    return aOrder - bOrder || a.localeCompare(b);\n  });\n  return unique;\n}\n\nexport function knowledgeScopeKey(scope: KnowledgeScope): string {\n  return canonicalizeKnowledgeScope(scope).join('\\u001f');","sourceCodeStart":277,"sourceCodeEnd":313,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/packages/core/src/storage/domains/knowledge/base.ts#L277-L313","documentation":"Scopes are strictly hierarchical: a thread-scoped entry ('thread:<id>') is only meaningful beneath its resource and org ancestors. canonicalizeKnowledgeScope therefore requires that any scope containing a thread entry also contains the corresponding resource and org entries.","triggerScenarios":"Calling any knowledge API with scope ['org:a', 'thread:t1'] (missing resource) or ['thread:t1'] alone; dropping the resource entry when narrowing a scope from resource to thread level.","commonSituations":"Constructing thread scope from just a threadId; copying a scope array and deleting the resource line for 'narrowing'; passing expandKnowledgeScope output of the wrong level back in as raw context.","solutions":["Always pass the full ancestor chain: ['org:<o>', 'resource:<r>', 'thread:<t>'].","Use expandKnowledgeScope(context, 'thread') to derive a properly ancestor-complete thread scope instead of hand-building it.","When narrowing, keep all wider-level entries and only swap the narrowest one.","Validate presence of every ancestor level in a helper before calling knowledge APIs."],"exampleFix":"// before\nconst scope = [`thread:${threadId}`];\n// after\nconst scope = [`org:${orgId}`, `resource:${resourceId}`, `thread:${threadId}`];","handlingStrategy":"validation","validationCode":"function hasThreadAncestors(scope: string[]): boolean {\n  const levels = new Set(scope.map(e => e.slice(0, e.indexOf(':'))));\n  if (levels.has('thread')) return levels.has('resource') && levels.has('org');\n  if (levels.has('resource')) return levels.has('org');\n  return levels.size > 0;\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always carry the full ancestor chain org -> resource -> thread in scope objects.","Derive thread scopes with expandKnowledgeScope(context, 'thread') instead of manual assembly.","When narrowing, replace only the narrowest level; keep ancestors."],"tags":["knowledge","scope","hierarchy","validation"],"backgroundTag":"invalid-scope-entry","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}