microsoft/typescript-go · error · ErrClientError

%w: snapshot %d not found

Error message

%w: snapshot %d not found

What it means

A request referenced a SnapshotID that is not in the Session's snapshots map: "snapshot N not found" (from getSnapshotData, the read path every language/compiler request takes). Snapshots are created by updateSnapshot/updateTemporarySnapshot, are reference-counted, and disappear when release drops the refCount to zero - after that, every handle minted inside that snapshot (symbols, types, signatures, project data) is dead too.

Source

Thrown at internal/api/session.go:455

}

// ProjectSession returns the underlying project session.
func (s *Session) ProjectSession() *project.Session {
	return s.projectSession
}

// snapshotHandle creates a snapshot handle from a snapshot's ID.
func snapshotHandle(snapshot *project.Snapshot) SnapshotID {
	return SnapshotID(snapshot.ID())
}

// getSnapshotData looks up snapshot data by handle.
func (s *Session) getSnapshotData(handle SnapshotID) (*snapshotData, error) {
	s.snapshotsMu.RLock()
	sd, ok := s.snapshots[handle]
	s.snapshotsMu.RUnlock()
	if !ok {
		return nil, fmt.Errorf("%w: snapshot %d not found", ErrClientError, handle)
	}
	return sd, nil
}

// retainSnapshotData pins snapshot data while an operation builds a derived snapshot.
func (s *Session) retainSnapshotData(handle SnapshotID) (*snapshotData, error) {
	s.snapshotsMu.Lock()
	defer s.snapshotsMu.Unlock()
	sd, ok := s.snapshots[handle]
	if !ok {
		return nil, fmt.Errorf("%w: snapshot %d not found", ErrClientError, handle)
	}
	sd.refCount++
	return sd, nil
}

func (s *Session) releaseSnapshot(handle SnapshotID) error {
	s.snapshotsMu.Lock()

View on GitHub (pinned to 1bcfa18d79)

Solutions

  1. Call updateSnapshot to obtain a fresh snapshot id and re-run the query against it
  2. Track snapshot lifecycle client-side: mark released snapshots and refuse to send requests against them
  3. Serialize release with in-flight requests: drain outstanding calls before releasing

Example fix

// before
release(snapshot); // refcount hits 0, snapshot dropped
await call("getTypeAtPosition", { snapshot, ... }); // snapshot not found

// after
await inFlight;          // drain queries first
release(snapshot);
snapshot = await call("updateSnapshot", { ... }); // fresh handle for later queries
Defensive patterns

Strategy: fallback

Validate before calling

// TS client: consult local snapshot bookkeeping before any request.
class SnapshotTracker {
  private live = new Set<bigint>();
  markLive(id: bigint) { this.live.add(id); }
  markReleased(id: bigint) { this.live.delete(id); }
  assertUsable(id: bigint) {
    if (!this.live.has(id)) throw new Error(`snapshot ${id} is released/unknown; run updateSnapshot first`);
  }
}

Type guard

const isLiveSnapshot = (id: bigint | number | undefined | null, live: Set<bigint>): id is bigint =>
  typeof id === "bigint" && id > 0n && live.has(id);

Try / catch

try {
  return await call(method, params);
} catch (e) {
  if (String(e).includes("snapshot") && String(e).includes(" not found")) {
    // snapshot gone: rebuild state from a fresh updateSnapshot and retry once
    const { snapshot } = await call("updateSnapshot", buildCurrentUpdate());
    params.snapshot = snapshot;
    return await call(method, params);
  }
  throw e;
}

Prevention

When it happens

Trigger: Issuing any snapshot-scoped request after the matching release call; using a snapshot id from before an updateSnapshot cycle completed; sending snapshot 0 from a default-initialized params struct; session restart resetting all ids while the client kept its state.

Common situations: Client bookkeeping bugs that release a snapshot then keep using it (e.g. debounced editor queries racing a dispose); background analyzers outliving their snapshot; reconnecting to a restarted server with stale handles.

Related errors


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