microsoft/typescript-go · error · ErrClientError

%w: symbol handle %d not found in snapshot registry

Error message

%w: symbol handle %d not found in snapshot registry

What it means

The SymbolID is non-zero but absent from this snapshot's symbolRegistry, so resolveSymbolHandle fails with "symbol handle N not found in snapshot registry". Symbols are registered snapshot-wide (ids come from a global counter, so the same symbol keeps one id), but the registry itself lives and dies with the snapshotData: once that snapshot is released or replaced, all its symbol handles become unresolvable.

Source

Thrown at internal/api/session.go:246

		}
		return id
	}
	reg.typeRegistry[id] = t
	return id
}

// resolveSymbolHandle resolves a symbol handle within the snapshot's registry.
func (sd *snapshotData) resolveSymbolHandle(handle SymbolID) (*ast.Symbol, error) {
	if handle == 0 {
		return nil, fmt.Errorf("%w: empty symbol handle", ErrClientError)
	}

	sd.symbolRegistryMu.RLock()
	symbol, ok := sd.symbolRegistry[handle]
	sd.symbolRegistryMu.RUnlock()

	if !ok {
		return nil, fmt.Errorf("%w: symbol handle %d not found in snapshot registry", ErrClientError, handle)
	}

	return symbol, nil
}

// resolveTypeHandle resolves a type handle within the project's registry.
func (sd *snapshotData) resolveTypeHandle(projectID ProjectID, handle TypeID) (*checker.Type, error) {
	if handle == 0 {
		return nil, fmt.Errorf("%w: empty type handle", ErrClientError)
	}
	if projectID == "" {
		return nil, fmt.Errorf("%w: empty project ID for type handle %d", ErrClientError, handle)
	}

	sd.projectRegistriesMu.RLock()
	reg := sd.projectRegistries[projectID]
	sd.projectRegistriesMu.RUnlock()

View on GitHub (pinned to 1bcfa18d79)

Solutions

  1. Re-acquire the symbol in the current snapshot (e.g. getSymbolAtPosition/getSymbolAtLocation) instead of reusing an old handle
  2. Invalidate all cached symbol handles whenever updateSnapshot returns a new snapshot id or release completes
  3. Keep handles paired with the snapshot id they came from and always send that snapshot in follow-ups

Example fix

// before
const symId = cache.get("hoverSymbol"); // from snapshot N-1
await call("getMembersOfSymbol", { snapshot: current, project, symbol: symId }); // not found

// after
const sym = await call("getSymbolAtPosition", { snapshot: current, project, file, position });
if (sym) await call("getMembersOfSymbol", { snapshot: current, project, symbol: sym.id });
Defensive patterns

Strategy: fallback

Validate before calling

// TS client: never send a symbol handle from another snapshot.
function symbolUsableIn(handle: {snapshot: bigint, id: number}, current: bigint): boolean {
  return handle.snapshot === current;
}
if (!symbolUsableIn(cached, snapshot)) {
  cached = await requerySymbolAtPosition(file, position); // refresh in current snapshot
}

Type guard

type SymbolRef = { snapshot: bigint; project: string; id: number };
const isResolvableSymbolRef = (r: SymbolRef | null | undefined, current: bigint): r is SymbolRef =>
  !!r && typeof r.id === "number" && r.id > 0 && r.snapshot === current;

Try / catch

try {
  return await call(method, params);
} catch (e) {
  if (String(e).includes("not found in snapshot registry")) {
    // handle is stale for this snapshot: re-acquire the symbol at its location and retry once
    const sym = await call("getSymbolAtPosition", { snapshot: params.snapshot, project: params.project, file, position });
    if (sym) return await call(method, { ...params, symbol: sym.id });
  }
  throw e;
}

Prevention

When it happens

Trigger: Using a SymbolID obtained from snapshot A in a request against snapshot B (before or after an updateSnapshot); calling release for the snapshot and then re-using its handles; ids carried over after a server restart (the counter and registry reset); ids from a different session/process; fabricated ids.

Common situations: Client caches symbols in editor decorations/hover state across document edits (each updateSnapshot mints a new snapshot); long-lived background analyzers holding symbol ids; session restart invalidating every stored handle.

Related errors


AI-assisted analysis of microsoft/typescript-go@1bcfa18d79 (2026-08-16). Data as JSON: /api/errors/5f2b71e929585aeb. Report an issue: GitHub.