{"record":{"id":"8d39d382e77d35ea","repo":"mastra-ai/mastra","slug":"pin-is-outside-the-visible-scope","errorCode":null,"errorMessage":"Pin is outside the visible scope.","messagePattern":"Pin is outside the visible scope\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/memory/src/processors/observational-memory/subconscious/pinned.ts","lineNumber":180,"sourceCode":"  });\n}\n\nasync function getStore(memory: PinnedMemory): Promise<KnowledgeStorage> {\n  const store = await memory.storage.getStore('knowledge');\n  if (!store) throw new Error('Pinned knowledge requires a configured knowledge storage domain.');\n  return store;\n}\n\nasync function requirePin(\n  store: KnowledgeStorage,\n  recordId: string,\n  options: PinnedToolsOptions,\n): Promise<KnowledgeRecord> {\n  const record = await store.getKnowledge({ id: recordId, includeDeleted: false });\n  if (!record) throw new Error(`Pin not found: ${recordId}`);\n  const nodeId = await resolvePinnedNodeId(store, options.scope);\n  if (!nodeId || record.node !== nodeId) throw new Error(`Record is not a pin: ${recordId}`);\n  if (!isKnowledgeScopeVisible(record.scope, options.scope)) throw new Error('Pin is outside the visible scope.');\n  return record;\n}\n\n/**\n * Pin lifecycle tools. Pin appends a record on the reserved node; unpin soft-deletes it\n * (auditable, restorable); edit is remove plus append because knowledge records are immutable,\n * so an edited pin carries a new record id.\n */\nexport function createPinnedTools(\n  memory: PinnedMemory,\n  options: PinnedToolsOptions,\n): Record<string, ToolAction<any, any, any>> {\n  return {\n    knowledge_pin: createTool({\n      id: 'knowledge_pin',\n      description:\n        'Pin knowledge that must stay in context every turn without being asked for. Pins cost context permanently; pin only what is unconditionally relevant.',\n      inputSchema: {","sourceCodeStart":162,"sourceCodeEnd":198,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/packages/memory/src/processors/observational-memory/subconscious/pinned.ts#L162-L198","documentation":"Thrown by `requirePin` in packages/memory/src/processors/observational-memory/subconscious/pinned.ts:180 when a pin record exists on the reserved pin node, but the record's knowledge scope is not visible within the caller's currently configured scope (`options.scope`). The library enforces scope isolation: a pin recorded under a different (e.g. broader or other-thread) scope cannot be operated on from the current scope context.","triggerScenarios":"Calling a pin lifecycle tool (via `record`, which calls `requirePin`) with a recordId whose record passes the `record.node === nodeId` check but whose `record.scope` fails `isKnowledgeScopeVisible(record.scope, options.scope)` — e.g. unpinning/editing a pin created under `org:X,resource:Y,thread:T1` while the subconscious now resolves scope for `thread:T2`, or the requestContext's organizationId/resourceId changed since the pin was created.","commonSituations":"Multi-tenant apps where organizationId or resourceId in requestContext changes between pin creation and usage; thread ID renames/migrations; invoking pinned-record tools from a different agent thread than the one that created the pin; restoring soft-deleted records across scope boundaries.","solutions":["Ensure the request context (organizationId, resourceId) and threadId used now match those under which the pin was created.","Check the pin's record.scope in the knowledge store before invoking the tool and only operate on pins whose scope is visible from your active scope.","If the pin must move, remove it from the old scope and re-append under the new scope (records are immutable; edit = remove + append)."],"exampleFix":"// before (scope mismatch, e.g. different thread)\nawait pinnedTool({ recordId, scope: ['org:acme','resource:bot','thread:thread-b'] });\n// after (use the scope the pin was created under)\nconst record = await store.getKnowledge({ id: recordId, includeDeleted: false });\nawait pinnedTool({ recordId, scope: record.scope });","handlingStrategy":"validation","validationCode":"const record = await store.getKnowledge({ id: recordId, includeDeleted: false });\nif (!record) throw new Error(`Pin not found: ${recordId}`);\nif (!record.scope.every((key, i) => options.scope[i] === key)) {\n  throw new Error(`Pin ${recordId} scope ${record.scope} is not visible from ${options.scope}`);\n}","typeGuard":"function isPinVisibleInScope(record, scope) {\n  return Array.isArray(record?.scope) && record.scope.every((key, i) => scope[i] === key);\n}","tryCatchPattern":"try {\n  await pinnedTool({ recordId, scope: currentScope });\n} catch (e) {\n  if (e.message === 'Pin is outside the visible scope.') {\n    // fall back to the pin's own scope or surface a user-facing 'not found' error\n  } else throw e;\n}","preventionTips":["Always derive operation scope from the same requestContext used when the pin was created.","Store scope metadata alongside your own references to pin recordIds.","Write tests covering cross-thread/cross-org pin access."],"tags":["scope-mismatch","memory","observational-memory"],"backgroundTag":"knowledge-scope-mismatch","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T08:17:16.595Z"}