microsoft/typescript-go · error · ErrClientError

%w: failed to update snapshot: %w

Error message

%w: failed to update snapshot: %w

What it means

updateSnapshot failed inside projectSession.APIUpdate, and the error is wrapped in ErrClientError even though causes can be environmental: unreadable or invalid tsconfig for an opened project, filesystem errors while loading files, or a canceled context. The server correctly Derefs the snapshot it still returns on the error path, so no client-side cleanup of a snapshot handle is needed for the failed call.

Source

Thrown at internal/api/session.go:995

		if !s.openFiles.Has(path) {
			continue
		}
		if apiRequest.CloseFiles == nil {
			apiRequest.CloseFiles = collections.NewSetWithSizeHint[tspath.Path](len(params.CloseFiles))
		}
		apiRequest.CloseFiles.Add(path)
		closedFiles = append(closedFiles, path)
	}

	// Even when nothing is opened or closed, APIUpdate ensures all projects and
	// files opened by the API are up to date. For an API connected to an LSP server,
	// this brings the API state up to date with the LSP state and ensures projects
	// the API cares about are ready to be queried.
	snapshot, err := s.projectSession.APIUpdate(ctx, fileChanges, apiRequest)
	if err != nil {
		// APIUpdate returns a ref'd snapshot even on error; release it.
		snapshot.Deref(s.projectSession)
		return nil, fmt.Errorf("%w: failed to update snapshot: %w", ErrClientError, err)
	}

	// Commit ref tracking now that the update succeeded.
	for _, configPath := range openedProjects {
		s.openProjects.Add(configPath)
	}
	for _, configPath := range closedProjects {
		s.openProjects.Delete(configPath)
	}
	for _, path := range openedFiles {
		s.openFiles.Add(path)
	}
	for _, path := range closedFiles {
		s.openFiles.Delete(path)
	}

	// Create or ref-count snapshot data, then atomically read the previous latest
	// snapshot (the diff base) and advance latestSnapshot to the new handle.

View on GitHub (pinned to 1bcfa18d79)

Solutions

  1. Inspect the wrapped error text to distinguish config/FS problems from cancellation
  2. Pre-validate the config with parseConfigFile/readConfigFile before opening the project
  3. For deadline cancellations, raise or remove the client timeout and retry the update
  4. Retry once after the filesystem condition is fixed; updateSnapshot is idempotent

Example fix

// before
ctx, cancel := context.WithTimeout(ctx, 500*time.Millisecond) // too tight for big projects
resp, err := updateSnapshot(ctx, changes)

// after
ctx, cancel := context.WithTimeout(ctx, 60*time.Second)
resp, err := updateSnapshot(ctx, changes)
Defensive patterns

Strategy: retry

Validate before calling

// Pre-validate configs of projects you are about to open.
for _, cfg := range projectsToOpen {
    if _, err := parseConfigFile(ctx, api.ParseConfigFileParams{File: api.DocumentIdentifier{FileName: cfg}}); err != nil {
        return fmt.Errorf("bad config %s: %w", cfg, err)
    }
}

Try / catch

resp, err := updateSnapshot(ctx, changes)
if err != nil {
    if ctx.Err() != nil || errors.Is(err, context.DeadlineExceeded) {
        // cancellation: retry with a longer deadline; updateSnapshot is idempotent
        resp, err = updateSnapshot(ctxLonger, changes)
    }
    if err != nil { return err } // config/FS problem: inspect wrapped error
}

Prevention

When it happens

Trigger: Opening a project whose tsconfig.json references unreadable files or contains invalid JSON; a context deadline hit while a large project loads; files disappearing mid-update; FS access errors from the host filesystem or overlay.

Common situations: Configs valid on the dev machine but broken in CI (missing node_modules, different case in imports); very large monorepos exceeding an aggressive client timeout; files deleted concurrently with the update.

Related errors


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