microsoft/typescript-go · error · ErrClientError

%w: failed to update temporary snapshot: %w

Error message

%w: failed to update temporary snapshot: %w

What it means

updateTemporarySnapshot failed inside projectSession.APIUpdateTemporary, wrapped in ErrClientError. This call builds a scratch snapshot by applying params.NewText at the given file URI on top of the base snapshot; it fails when the base snapshot does not contain the file/project state needed (e.g. the file was never opened into that snapshot) or when the context is canceled.

Source

Thrown at internal/api/session.go:1075

		Changes:  changes,
	}, nil
}

// handleUpdateTemporarySnapshot creates a temporary snapshot that overrides the
// content of a single file, without opening/closing any projects or files and
// without advancing the session's latest snapshot.
func (s *Session) handleUpdateTemporarySnapshot(ctx context.Context, params *UpdateTemporarySnapshotParams) (*UpdateSnapshotResponse, error) {
	baseSD, err := s.retainSnapshotData(params.Snapshot)
	if err != nil {
		return nil, err
	}
	defer func() { _ = s.releaseSnapshot(params.Snapshot) }()

	uri := params.File.ToURI(s.projectSession.GetCurrentDirectory())

	snapshot, err := s.projectSession.APIUpdateTemporary(ctx, baseSD.snapshot, uri, params.NewText)
	if err != nil {
		return nil, fmt.Errorf("%w: failed to update temporary snapshot: %w", ErrClientError, err)
	}

	handle := snapshotHandle(snapshot)
	s.snapshotsMu.Lock()
	sd, exists := s.snapshots[handle]
	if exists {
		snapshot.Deref(s.projectSession)
		sd.refCount++
	} else {
		sd = &snapshotData{
			snapshot:                snapshot,
			refCount:                1,
			symbolRegistry:          make(map[SymbolID]*ast.Symbol),
			symbolCanonicalProjects: make(map[SymbolID]ProjectID),
			projectRegistries:       make(map[ProjectID]*projectRegistryData),
		}
		s.snapshots[handle] = sd
	}

View on GitHub (pinned to 1bcfa18d79)

Solutions

  1. Ensure the target file was opened (openedFiles) in an updateSnapshot and use the handle from that response as params.Snapshot
  2. Do not reuse snapshot handles after calling release on them
  3. Retry with a fresh updateSnapshot if the editor reopens the file

Example fix

// before
updateSnapshot(ctx, changes) // changes does NOT include the file
updateTemporarySnapshot(base, file, newText) // fails

// after
resp, _ := updateSnapshot(ctx, changesWithFileOpened)
updateTemporarySnapshot(resp.Snapshot, file, newText)
Defensive patterns

Strategy: validation

Validate before calling

// Ensure the file is open in the base snapshot before a temporary update.
opened := false
for _, f := range lastUpdateResponse.Changes /* or your tracked openedFiles */ {
    if f == targetFile { opened = true }
}
if !opened {
    resp, err := updateSnapshot(ctx, openFile(targetFile))
    if err != nil { return err }
    baseSnapshot = resp.Snapshot
}

Type guard

func canUpdateTemporary(base api.SnapshotID, baseValid bool, fileOpened bool) bool {
    return base != 0 && baseValid && fileOpened
}

Try / catch

if err != nil {
    if strings.Contains(err.Error(), "temporary snapshot") {
        // re-open the file via updateSnapshot, then retry with the fresh handle
    }
    return err
}

Prevention

When it happens

Trigger: Calling updateTemporarySnapshot for a file that was not passed via openedFiles in the base updateSnapshot; using a base snapshot handle that was already released or superseded; cancellation mid-reparse.

Common situations: Completion-style flows that send didChange-style text without a prior open; client state machines that reuse a stale snapshot after the editor closed and reopened the file.

Related errors


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