{"record":{"id":"247735748e0ff0c2","repo":"mastra-ai/mastra","slug":"stale-handle","errorCode":"stale-handle","errorMessage":"Knowledge scope changed while the request was running.","messagePattern":"Knowledge scope changed while the request was running\\.","errorType":"error_code","errorClass":"KnowledgeInspectorError","httpStatus":null,"severity":"error","filePath":"mastracode/sdk/src/knowledge-inspector.ts","lineNumber":490,"sourceCode":"      this.#cursors.clear();\n    }\n    return { ownerId, resourceId, threadId, fingerprint, identityKey: this.#identityKey };\n  }\n\n  #scope(binding: Binding, level: KnowledgeInspectorScopeLevel): KnowledgeScope {\n    if (level === 'org') return [`org:${binding.ownerId}`];\n    const scope = [`org:${binding.ownerId}`, `resource:${binding.resourceId}`];\n    if (level === 'resource') return scope;\n    if (!binding.threadId) {\n      throw new KnowledgeInspectorError('unavailable', 'The active thread does not belong to this project.');\n    }\n    return [...scope, `thread:${binding.threadId}`];\n  }\n\n  async #assertStable(binding: Binding): Promise<void> {\n    const current = await this.#binding();\n    if (current.identityKey !== binding.identityKey || current.fingerprint !== binding.fingerprint) {\n      throw new KnowledgeInspectorError('stale-handle', 'Knowledge scope changed while the request was running.');\n    }\n  }\n\n  #assertVisible<T extends KnowledgeNode>(record: T | null, scope: KnowledgeScope): asserts record is T {\n    if (!record || !isKnowledgeScopeVisible(record.scope, scope)) {\n      throw new KnowledgeInspectorError('not-visible', 'Knowledge record is not visible in the selected scope.');\n    }\n  }\n\n  #recordSummary(\n    record: KnowledgeNode,\n    binding: Binding,\n    level: KnowledgeInspectorScopeLevel,\n  ): KnowledgeInspectorNodeSummary {\n    const type: KnowledgeInspectorRecordType = 'node';\n    return {\n      handle: this.#mintHandle(binding, level, type, record.id),\n      type,","sourceCodeStart":472,"sourceCodeEnd":508,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/mastracode/sdk/src/knowledge-inspector.ts#L472-L508","documentation":"The inspector stamps each request with an opaque identityKey and a fingerprint (owner\\0resource\\0thread). #assertStable() re-runs #binding() at the end of a request and throws 'stale-handle' if the fingerprint or identity key changed mid-request. This guarantees a response reflects one consistent scope even if the session's owner/project/thread changed while queries were running.","triggerScenarios":"During an async inspector call (list/read/activity), the session's owner, project, or active thread changed — e.g. user switched project or thread — so #binding() regenerated the fingerprint and a new identityKey, invalidating the in-flight request's binding.","commonSituations":"Long-running knowledge queries racing a project switch in the TUI; a thread rotation completing while pagination fetches the next page; concurrent inspector calls while identity is refreshed.","solutions":["Retry the request after the scope change settles; the new call will bind to the current scope and return fresh handles/cursors.","Avoid switching project/thread while knowledge queries are in flight, or serialize inspector calls with session state changes.","Catch KnowledgeInspectorError code 'stale-handle' and surface 'scope changed' to the user, prompting a re-query."],"exampleFix":"// before\nconst page = await inspector.listNodes({ level: 'resource' }); // may throw if user switches project mid-call\n// after\ntry {\n  const page = await inspector.listNodes({ level: 'resource' });\n} catch (e) {\n  if (e instanceof KnowledgeInspectorError && e.code === 'stale-handle') {\n    return inspector.listNodes({ level: 'resource' }); // re-bind to current scope\n  }\n  throw e;\n}","handlingStrategy":"retry","validationCode":"// Validate scope stability before issuing long-running calls\nconst before = session.identity.getResourceId();\n// ... after any await that may allow UI state change, re-check:\n// if (session.identity.getResourceId() !== before) restart the request","typeGuard":null,"tryCatchPattern":"async function withStableRetry<T>(fn: () => Promise<T>, retries = 2): Promise<T> {\n  try {\n    return await fn();\n  } catch (e) {\n    if (e instanceof KnowledgeInspectorError && e.code === 'stale-handle' && retries > 0) {\n      return withStableRetry(fn, retries - 1); // re-binds to current scope\n    }\n    throw e;\n  }\n}","preventionTips":["Block project/thread switching while knowledge requests are in flight (disable switcher during queries).","Treat 'stale-handle' from any inspector call as retryable, not fatal.","Keep inspector calls short-lived; paginate rather than issuing one giant request."],"tags":["knowledge-inspector","race-condition","stale-state"],"backgroundTag":"stale-handle","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}