{"record":{"id":"4c9f48852bd553ba","repo":"microsoft/typescript-go","slug":"w-snapshot-d-not-found","errorCode":null,"errorMessage":"%w: snapshot %d not found","messagePattern":"%w: snapshot (.+?) not found","errorType":"validation","errorClass":"ErrClientError","httpStatus":null,"severity":"error","filePath":"internal/api/session.go","lineNumber":455,"sourceCode":"}\n\n// ProjectSession returns the underlying project session.\nfunc (s *Session) ProjectSession() *project.Session {\n\treturn s.projectSession\n}\n\n// snapshotHandle creates a snapshot handle from a snapshot's ID.\nfunc snapshotHandle(snapshot *project.Snapshot) SnapshotID {\n\treturn SnapshotID(snapshot.ID())\n}\n\n// getSnapshotData looks up snapshot data by handle.\nfunc (s *Session) getSnapshotData(handle SnapshotID) (*snapshotData, error) {\n\ts.snapshotsMu.RLock()\n\tsd, ok := s.snapshots[handle]\n\ts.snapshotsMu.RUnlock()\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"%w: snapshot %d not found\", ErrClientError, handle)\n\t}\n\treturn sd, nil\n}\n\n// retainSnapshotData pins snapshot data while an operation builds a derived snapshot.\nfunc (s *Session) retainSnapshotData(handle SnapshotID) (*snapshotData, error) {\n\ts.snapshotsMu.Lock()\n\tdefer s.snapshotsMu.Unlock()\n\tsd, ok := s.snapshots[handle]\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"%w: snapshot %d not found\", ErrClientError, handle)\n\t}\n\tsd.refCount++\n\treturn sd, nil\n}\n\nfunc (s *Session) releaseSnapshot(handle SnapshotID) error {\n\ts.snapshotsMu.Lock()","sourceCodeStart":437,"sourceCodeEnd":473,"githubUrl":"https://github.com/microsoft/typescript-go/blob/1bcfa18d79a3be41772223d5c05dfe4480e614ff/internal/api/session.go#L437-L473","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Call updateSnapshot to obtain a fresh snapshot id and re-run the query against it","Track snapshot lifecycle client-side: mark released snapshots and refuse to send requests against them","Serialize release with in-flight requests: drain outstanding calls before releasing"],"exampleFix":"// before\nrelease(snapshot); // refcount hits 0, snapshot dropped\nawait call(\"getTypeAtPosition\", { snapshot, ... }); // snapshot not found\n\n// after\nawait inFlight;          // drain queries first\nrelease(snapshot);\nsnapshot = await call(\"updateSnapshot\", { ... }); // fresh handle for later queries","handlingStrategy":"fallback","validationCode":"// TS client: consult local snapshot bookkeeping before any request.\nclass SnapshotTracker {\n  private live = new Set<bigint>();\n  markLive(id: bigint) { this.live.add(id); }\n  markReleased(id: bigint) { this.live.delete(id); }\n  assertUsable(id: bigint) {\n    if (!this.live.has(id)) throw new Error(`snapshot ${id} is released/unknown; run updateSnapshot first`);\n  }\n}","typeGuard":"const isLiveSnapshot = (id: bigint | number | undefined | null, live: Set<bigint>): id is bigint =>\n  typeof id === \"bigint\" && id > 0n && live.has(id);","tryCatchPattern":"try {\n  return await call(method, params);\n} catch (e) {\n  if (String(e).includes(\"snapshot\") && String(e).includes(\" not found\")) {\n    // snapshot gone: rebuild state from a fresh updateSnapshot and retry once\n    const { snapshot } = await call(\"updateSnapshot\", buildCurrentUpdate());\n    params.snapshot = snapshot;\n    return await call(method, params);\n  }\n  throw e;\n}","preventionTips":["Maintain a released-snapshots set client-side; refuse requests against it","Drain in-flight requests before calling release","On server restart, drop all cached handles and re-initialize the session"],"tags":["snapshot","handle","lifecycle","release","stale-handle","client-error"],"backgroundTag":null,"analyzedSha":"1bcfa18d79a3be41772223d5c05dfe4480e614ff","analyzedAt":"2026-08-16T02:12:00.115Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}