{"record":{"id":"f230bd58931defe0","repo":"microsoft/typescript-go","slug":"w-failed-to-update-snapshot-w","errorCode":null,"errorMessage":"%w: failed to update snapshot: %w","messagePattern":"%w: failed to update snapshot: %w","errorType":"error_code","errorClass":"ErrClientError","httpStatus":null,"severity":"error","filePath":"internal/api/session.go","lineNumber":995,"sourceCode":"\t\tif !s.openFiles.Has(path) {\n\t\t\tcontinue\n\t\t}\n\t\tif apiRequest.CloseFiles == nil {\n\t\t\tapiRequest.CloseFiles = collections.NewSetWithSizeHint[tspath.Path](len(params.CloseFiles))\n\t\t}\n\t\tapiRequest.CloseFiles.Add(path)\n\t\tclosedFiles = append(closedFiles, path)\n\t}\n\n\t// Even when nothing is opened or closed, APIUpdate ensures all projects and\n\t// files opened by the API are up to date. For an API connected to an LSP server,\n\t// this brings the API state up to date with the LSP state and ensures projects\n\t// the API cares about are ready to be queried.\n\tsnapshot, err := s.projectSession.APIUpdate(ctx, fileChanges, apiRequest)\n\tif err != nil {\n\t\t// APIUpdate returns a ref'd snapshot even on error; release it.\n\t\tsnapshot.Deref(s.projectSession)\n\t\treturn nil, fmt.Errorf(\"%w: failed to update snapshot: %w\", ErrClientError, err)\n\t}\n\n\t// Commit ref tracking now that the update succeeded.\n\tfor _, configPath := range openedProjects {\n\t\ts.openProjects.Add(configPath)\n\t}\n\tfor _, configPath := range closedProjects {\n\t\ts.openProjects.Delete(configPath)\n\t}\n\tfor _, path := range openedFiles {\n\t\ts.openFiles.Add(path)\n\t}\n\tfor _, path := range closedFiles {\n\t\ts.openFiles.Delete(path)\n\t}\n\n\t// Create or ref-count snapshot data, then atomically read the previous latest\n\t// snapshot (the diff base) and advance latestSnapshot to the new handle.","sourceCodeStart":977,"sourceCodeEnd":1013,"githubUrl":"https://github.com/microsoft/typescript-go/blob/1bcfa18d79a3be41772223d5c05dfe4480e614ff/internal/api/session.go#L977-L1013","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Inspect the wrapped error text to distinguish config/FS problems from cancellation","Pre-validate the config with parseConfigFile/readConfigFile before opening the project","For deadline cancellations, raise or remove the client timeout and retry the update","Retry once after the filesystem condition is fixed; updateSnapshot is idempotent"],"exampleFix":"// before\nctx, cancel := context.WithTimeout(ctx, 500*time.Millisecond) // too tight for big projects\nresp, err := updateSnapshot(ctx, changes)\n\n// after\nctx, cancel := context.WithTimeout(ctx, 60*time.Second)\nresp, err := updateSnapshot(ctx, changes)","handlingStrategy":"retry","validationCode":"// Pre-validate configs of projects you are about to open.\nfor _, cfg := range projectsToOpen {\n    if _, err := parseConfigFile(ctx, api.ParseConfigFileParams{File: api.DocumentIdentifier{FileName: cfg}}); err != nil {\n        return fmt.Errorf(\"bad config %s: %w\", cfg, err)\n    }\n}","typeGuard":null,"tryCatchPattern":"resp, err := updateSnapshot(ctx, changes)\nif err != nil {\n    if ctx.Err() != nil || errors.Is(err, context.DeadlineExceeded) {\n        // cancellation: retry with a longer deadline; updateSnapshot is idempotent\n        resp, err = updateSnapshot(ctxLonger, changes)\n    }\n    if err != nil { return err } // config/FS problem: inspect wrapped error\n}","preventionTips":["Give updateSnapshot deadlines sized to project size (large monorepos need seconds-to-minutes)","Validate tsconfig files before opening their projects","Do not delete or move workspace files concurrently with snapshot updates"],"tags":["snapshot","project-loading","filesystem","context","api"],"backgroundTag":null,"analyzedSha":"1bcfa18d79a3be41772223d5c05dfe4480e614ff","analyzedAt":"2026-08-16T02:12:00.115Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}