{"record":{"id":"258b8eb08cf718f0","repo":"microsoft/typescript-go","slug":"w-empty-handle","errorCode":null,"errorMessage":"%w: empty handle","messagePattern":"%w: empty handle","errorType":"validation","errorClass":"ErrClientError","httpStatus":null,"severity":"error","filePath":"internal/api/session.go","lineNumber":1121,"sourceCode":"\t\tprojectResponses = append(projectResponses, NewProjectResponse(proj))\n\t}\n\n\t// Compute changes from the requested base snapshot so the client can retain\n\t// cached source files for unchanged files.\n\tchanges := computeSnapshotChanges(baseSD.snapshot, snapshot)\n\n\treturn &UpdateSnapshotResponse{\n\t\tSnapshot: handle,\n\t\tProjects: projectResponses,\n\t\tChanges:  changes,\n\t}, nil\n}\n\n// handleRelease decrements the ref count for a snapshot.\n// The snapshot and its registries are only cleaned up when the ref count reaches zero.\nfunc (s *Session) handleRelease(ctx context.Context, params *ReleaseParams) (any, error) {\n\tif params == nil || params.Snapshot == 0 {\n\t\treturn nil, fmt.Errorf(\"%w: empty handle\", ErrClientError)\n\t}\n\n\tif err := s.releaseSnapshot(params.Snapshot); err != nil {\n\t\treturn nil, err\n\t}\n\treturn true, nil\n}\n\n// handleGetDefaultProjectForFile returns the default project for a given file,\n// or nil if no project currently contains the file.\nfunc (s *Session) handleGetDefaultProjectForFile(ctx context.Context, params *GetDefaultProjectForFileParams) (*ProjectResponse, error) {\n\tsd, err := s.getSnapshotData(params.Snapshot)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\turi := params.File.ToURI(s.projectSession.GetCurrentDirectory())\n\tproj := sd.snapshot.GetDefaultProject(uri)","sourceCodeStart":1103,"sourceCodeEnd":1139,"githubUrl":"https://github.com/microsoft/typescript-go/blob/1bcfa18d79a3be41772223d5c05dfe4480e614ff/internal/api/session.go#L1103-L1139","documentation":"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\").","triggerScenarios":"Sending {} or null to release; omitting the snapshot key in the payload; a client variable left uninitialized when no prior updateSnapshot ran.","commonSituations":"Cleanup paths that release unconditionally on error, before any snapshot was ever obtained; refcount tracking that loses the handle when earlier calls failed.","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"],"exampleFix":"// before\nif err != nil { release(0) } // no snapshot ever acquired\n\n// after\nif err != nil { if snap != 0 { release(snap) }; return err }","handlingStrategy":"validation","validationCode":"func releaseIfHeld(snap *api.SnapshotID) error {\n    if snap == nil || *snap == 0 { return nil } // nothing acquired\n    _, err := session.HandleRequest(ctx, string(api.MethodRelease), api.ReleaseParams{Snapshot: *snap})\n    if err == nil { *snap = 0 }\n    return err\n}","typeGuard":"func isLiveSnapshot(id api.SnapshotID) bool { return id != 0 }","tryCatchPattern":"if _, err := session.HandleRequest(ctx, string(api.MethodRelease), params); err != nil {\n    if strings.Contains(err.Error(), \"empty handle\") {\n        // request-shape bug: your snapshot field was omitted; fix the caller, do not retry\n    }\n    return err\n}","preventionTips":["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"],"tags":["snapshot","release","validation","api"],"backgroundTag":null,"analyzedSha":"1bcfa18d79a3be41772223d5c05dfe4480e614ff","analyzedAt":"2026-08-16T02:12:00.115Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}