mastra-ai/mastra · error · KnowledgeInspectorError
invalid-handle
invalid-handle
Error message
Knowledge record handle is invalid or expired.
What it means
Knowledge record handles are opaque in-memory tokens with a TTL (HANDLE_TTL_MS) and an expected record type. #readHandle() throws 'invalid-handle' when the token is unknown, expired (pruned after expiresAt), evicted by the MAX_OPAQUE_ENTRIES cap, or used with the wrong expectedType (e.g. a node handle passed where an event handle is expected).
Source
Thrown at mastracode/sdk/src/knowledge-inspector.ts:689
type: KnowledgeInspectorRecordType,
recordId: string,
): string {
this.#pruneOpaqueEntries();
const token = opaqueToken();
this.#handles.set(token, {
identityKey: binding.identityKey,
level,
type,
recordId,
expiresAt: Date.now() + HANDLE_TTL_MS,
});
return token;
}
#readHandle(handle: string, binding: Binding, expectedType: KnowledgeInspectorRecordType): HandleEntry {
const entry = this.#handles.get(handle);
if (!entry || entry.expiresAt < Date.now() || entry.type !== expectedType) {
throw new KnowledgeInspectorError('invalid-handle', 'Knowledge record handle is invalid or expired.');
}
if (entry.identityKey !== binding.identityKey) {
throw new KnowledgeInspectorError('stale-handle', 'Knowledge record handle belongs to a previous scope.');
}
return entry;
}
#mintCursor(
binding: Binding,
level: KnowledgeInspectorScopeLevel,
kind: CursorEntry['kind'],
value: string,
filters?: CursorEntry['filters'],
): string {
this.#pruneOpaqueEntries();
const token = opaqueToken();
this.#cursors.set(token, {
identityKey: binding.identityKey,View on GitHub (pinned to 75dd419e61)
Solutions
- Re-list or re-query to mint a fresh handle instead of reusing an old one.
- Use handles promptly after they are returned; treat them as short-lived opaque tokens, never persist them.
- Catch KnowledgeInspectorError code 'invalid-handle', refresh the listing, and retry once with the new handle.
Example fix
// before
const savedHandle = fs.readFileSync('handle.txt', 'utf8'); // persisted across runs
await inspector.getNode(savedHandle);
// after
const { entries } = await inspector.listNodes({ level: 'resource' });
await inspector.getNode(entries[0].handle); // fresh handle from current binding Defensive patterns
Strategy: try-catch
Try / catch
try {
return await inspector.getNode(handle);
} catch (e) {
if (e instanceof KnowledgeInspectorError && e.code === 'invalid-handle') {
const { entries } = await inspector.listNodes({ level });
return entries.find(n => n.name === lastName) ?? null; // refresh and re-locate
}
throw e;
} Prevention
- Treat handles as ephemeral: use immediately, never persist to disk or DB.
- Refresh listings rather than caching handles across idle periods (TTL-bounded).
- Pass the correct handle type to each API (node handle for node reads, event handle for activity reads).
When it happens
Trigger: Calling a read/detail API with: a handle minted by a different inspector instance, a handle older than the TTL, a handle after the inspector's scope changed (handles cleared), or a node handle where an event/activity handle type is required.
Common situations: Storing handles long-term (persisting them across restarts); app idle longer than the TTL; holding many handles so the size cap evicts older ones; copy-pasting a handle into the wrong API.
Related errors
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/d2c2fad5ef5ea6c0.
Report an issue: GitHub.