microsoft/typescript-go · error · ErrClientError
%w: empty handle
Error message
%w: empty handle
What it means
release rejects params that are nil or carry Snapshot == 0. Snapshot handles are nonzero IDs minted by the server (snapshotHandle of the project snapshot's ID), so a zero value means the JSON omitted the snapshot field and it decoded to the Go zero value. This is a request-shape bug, not a double-release; releasing an already-released handle produces a different error ("snapshot N not found").
Source
Thrown at internal/api/session.go:1121
projectResponses = append(projectResponses, NewProjectResponse(proj))
}
// Compute changes from the requested base snapshot so the client can retain
// cached source files for unchanged files.
changes := computeSnapshotChanges(baseSD.snapshot, snapshot)
return &UpdateSnapshotResponse{
Snapshot: handle,
Projects: projectResponses,
Changes: changes,
}, nil
}
// handleRelease decrements the ref count for a snapshot.
// The snapshot and its registries are only cleaned up when the ref count reaches zero.
func (s *Session) handleRelease(ctx context.Context, params *ReleaseParams) (any, error) {
if params == nil || params.Snapshot == 0 {
return nil, fmt.Errorf("%w: empty handle", ErrClientError)
}
if err := s.releaseSnapshot(params.Snapshot); err != nil {
return nil, err
}
return true, nil
}
// handleGetDefaultProjectForFile returns the default project for a given file,
// or nil if no project currently contains the file.
func (s *Session) handleGetDefaultProjectForFile(ctx context.Context, params *GetDefaultProjectForFileParams) (*ProjectResponse, error) {
sd, err := s.getSnapshotData(params.Snapshot)
if err != nil {
return nil, err
}
uri := params.File.ToURI(s.projectSession.GetCurrentDirectory())
proj := sd.snapshot.GetDefaultProject(uri)View on GitHub (pinned to 1bcfa18d79)
Solutions
- Only call release when you hold a real handle from an UpdateSnapshotResponse
- Set your local handle variable to zero after a successful release and skip zero values
- On error paths, release only handles that were actually acquired
Example fix
// before
if err != nil { release(0) } // no snapshot ever acquired
// after
if err != nil { if snap != 0 { release(snap) }; return err } Defensive patterns
Strategy: validation
Validate before calling
func releaseIfHeld(snap *api.SnapshotID) error {
if snap == nil || *snap == 0 { return nil } // nothing acquired
_, err := session.HandleRequest(ctx, string(api.MethodRelease), api.ReleaseParams{Snapshot: *snap})
if err == nil { *snap = 0 }
return err
} Type guard
func isLiveSnapshot(id api.SnapshotID) bool { return id != 0 } Try / catch
if _, err := session.HandleRequest(ctx, string(api.MethodRelease), params); err != nil {
if strings.Contains(err.Error(), "empty handle") {
// request-shape bug: your snapshot field was omitted; fix the caller, do not retry
}
return err
} Prevention
- Zero out snapshot handles after successful release and guard zero before releasing
- Acquire-then-release in a defer pair within one function scope where possible
- On error paths, release only handles that were actually returned to you
When it happens
Trigger: Sending {} or null to release; omitting the snapshot key in the payload; a client variable left uninitialized when no prior updateSnapshot ran.
Common situations: Cleanup paths that release unconditionally on error, before any snapshot was ever obtained; refcount tracking that loses the handle when earlier calls failed.
Related errors
- Cannot run a temporary file update on an inactive snapshot
- Snapshot is disposed
- Cannot create directory: a file already exists at "/${segmen
- Invalid file path: "${path}"
- Bad line number. Line: ${line}, lineStarts.length: ${lineSta
AI-assisted analysis of microsoft/typescript-go@1bcfa18d79 (2026-08-16).
Data as JSON: /api/errors/258b8eb08cf718f0.
Report an issue: GitHub.