microsoft/typescript-go · error · Error

Snapshot is disposed

Error message

Snapshot is disposed

What it means

Thrown by Snapshot.ensureNotDisposed() and surfaced from getProjects(), getProject(), and getDefaultProjectForFile() after dispose() ran. dispose() sets disposed=true, clears the project map and object registry, and sends a release request for the server-side snapshot; any later use of that Snapshot object throws immediately.

Source

Thrown at _packages/native-preview/src/api/async/api.ts:437

    async dispose(): Promise<void> {
        if (this.disposed) return;
        this.disposed = true;
        for (const project of this.projectMap.values()) {
            project.dispose();
        }
        this.projectMap.clear();
        this.snapshotRegistry.clear();
        this.onDispose();
        await this.client.apiRequest("release", { snapshot: this.id });
    }

    isDisposed(): boolean {
        return this.disposed;
    }

    private ensureNotDisposed(): void {
        if (this.disposed) {
            throw new Error("Snapshot is disposed");
        }
    }
}

class SnapshotObjectRegistry {
    private readonly symbols: Map<number, Symbol> = new Map();
    private readonly client: Client;
    private readonly snapshotId: number;
    private readonly resolveProject: (projectId: Path) => Project | undefined;

    constructor(client: Client, snapshotId: number, resolveProject: (projectId: Path) => Project | undefined) {
        this.client = client;
        this.snapshotId = snapshotId;
        this.resolveProject = resolveProject;
    }

    /** Resolve a project id (a config file path) to its Project within this snapshot. */
    getProject(projectId: Path): Project | undefined {

View on GitHub (pinned to 1bcfa18d79)

Solutions

  1. Reacquire state: call api.updateSnapshot() again and query the new Snapshot instead of the disposed one
  2. Guard every use with snapshot.isDisposed() (public) and refresh when true
  3. Never leak the temporary snapshot from runWithTemporaryFileUpdate's callback - extract plain data before it returns
  4. Tear down dependents in the same finally that disposes the snapshot so nothing queries it afterwards

Example fix

// before
const projects = snapshot.getProjects(); // throws if disposed earlier

// after
if (snapshot.isDisposed()) {
    snapshot = await api.updateSnapshot(); // reacquire fresh state
}
const projects = snapshot.getProjects();
Defensive patterns

Strategy: validation

Validate before calling

if (snapshot.isDisposed()) {
    snapshot = await api.updateSnapshot(); // reacquire before querying
}
const projects = snapshot.getProjects();

Try / catch

try {
    return snapshot.getDefaultProjectForFile(file);
} catch (e) {
    if (e instanceof Error && e.message === 'Snapshot is disposed') {
        const fresh = await api.updateSnapshot();
        return fresh.getDefaultProjectForFile(file); // retry on fresh state
    }
    throw e;
}

Prevention

When it happens

Trigger: Holding a Snapshot across a subsequent updateSnapshot and continuing to query the old one after disposing it; using the temporary snapshot handed to a runWithTemporaryFileUpdate callback outside/after the callback (it is disposed in the finally block); touching snapshots after API.close(); double-lifecycle where app code disposed the snapshot then kept references.

Common situations: Editor-tooling loops that cache project lists from an old snapshot; async code that awaits past a dispose boundary (file closed, server session ended) and then calls getDefaultProjectForFile; tests reusing one snapshot for many mutations.

Related errors


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