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

  1. Re-list or re-query to mint a fresh handle instead of reusing an old one.
  2. Use handles promptly after they are returned; treat them as short-lived opaque tokens, never persist them.
  3. 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

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.