{"record":{"id":"5c9b0df0e34c3d27","repo":"microsoft/typescript-go","slug":"snapshot-is-disposed-5c9b0d","errorCode":null,"errorMessage":"Snapshot is disposed","messagePattern":"Snapshot is disposed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"_packages/native-preview/src/api/sync/api.ts","lineNumber":445,"sourceCode":"    dispose(): void {\r\n        if (this.disposed) return;\r\n        this.disposed = true;\r\n        for (const project of this.projectMap.values()) {\r\n            project.dispose();\r\n        }\r\n        this.projectMap.clear();\r\n        this.snapshotRegistry.clear();\r\n        this.onDispose();\r\n        this.client.apiRequest(\"release\", { snapshot: this.id });\r\n    }\r\n\r\n    isDisposed(): boolean {\r\n        return this.disposed;\r\n    }\r\n\r\n    private ensureNotDisposed(): void {\r\n        if (this.disposed) {\r\n            throw new Error(\"Snapshot is disposed\");\r\n        }\r\n    }\r\n}\r\n\r\nclass SnapshotObjectRegistry {\r\n    private readonly symbols: Map<number, Symbol> = new Map();\r\n    private readonly client: Client;\r\n    private readonly snapshotId: number;\r\n    private readonly resolveProject: (projectId: Path) => Project | undefined;\r\n\r\n    constructor(client: Client, snapshotId: number, resolveProject: (projectId: Path) => Project | undefined) {\r\n        this.client = client;\r\n        this.snapshotId = snapshotId;\r\n        this.resolveProject = resolveProject;\r\n    }\r\n\r\n    /** Resolve a project id (a config file path) to its Project within this snapshot. */\r\n    getProject(projectId: Path): Project | undefined {\r","sourceCodeStart":427,"sourceCodeEnd":463,"githubUrl":"https://github.com/microsoft/typescript-go/blob/1bcfa18d79a3be41772223d5c05dfe4480e614ff/_packages/native-preview/src/api/sync/api.ts#L427-L463","documentation":"Snapshot.ensureNotDisposed guards every operation on a Snapshot object. Once dispose() runs — explicitly, via API.close(), or because the snapshot was superseded — the object releases its server-side resources (apiRequest('release')) and refuses further use by throwing 'Snapshot is disposed'. This is a use-after-free guard for the RPC-backed snapshot lifecycle.","triggerScenarios":"Calling any method on a Snapshot after snapshot.dispose(); calling methods on the latest snapshot after api.close() disposed all snapshots; using a snapshot captured before an updateFiles/sync cycle replaced it.","commonSituations":"Long-lived editor integrations caching snapshots across document changes; error paths that dispose then continue processing; concurrent handlers sharing a Snapshot without coordinating disposal.","solutions":["Call snapshot.isDisposed() before each use and refetch a current snapshot when disposed","Treat snapshots as short-lived: acquire, query, dispose — never cache across sync cycles","Audit dispose() call sites so only the snapshot's owner disposes it"],"exampleFix":"// before\nconst info = snapshot.getQuickInfo(pos); // throws if disposed\n\n// after\nif (snapshot.isDisposed()) snapshot = api.getProjectSnapshot(project);\nconst info = snapshot.getQuickInfo(pos);","handlingStrategy":"validation","validationCode":"if (snapshot.isDisposed()) { snapshot = api.getProjectSnapshot(\"tsconfig.json\"); }","typeGuard":null,"tryCatchPattern":"try { result = snapshot.someQuery(pos); } catch (e) { if ((e as Error).message === \"Snapshot is disposed\") { snapshot = refreshSnapshot(); result = snapshot.someQuery(pos); } else throw e; }","preventionTips":["Treat snapshots as ephemeral: acquire, query, drop","Gate every use with isDisposed()","Coordinate disposal ownership so only one path disposes a snapshot"],"tags":["typescript","snapshot","lifecycle","use-after-free"],"backgroundTag":null,"analyzedSha":"1bcfa18d79a3be41772223d5c05dfe4480e614ff","analyzedAt":"2026-08-16T02:12:00.115Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}