{"record":{"id":"abc39e9cda5bbb26","repo":"pulumi/pulumi","slug":"closing-temp-file-w","errorCode":null,"errorMessage":"closing temp file: %w","messagePattern":"closing temp file: %w","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/cmd/pulumi/cloud/spec_fetch.go","lineNumber":142,"sourceCode":"\n// writeCachedSpec writes data to path atomically via tempfile + rename. The\n// parent directory is created if it doesn't exist.\nfunc writeCachedSpec(path string, data []byte) error {\n\tif err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil {\n\t\treturn fmt.Errorf(\"creating cache dir: %w\", err)\n\t}\n\ttmp, err := os.CreateTemp(filepath.Dir(path), \"spec-*.json.tmp\")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"creating temp file: %w\", err)\n\t}\n\ttmpPath := tmp.Name()\n\tdefer os.Remove(tmpPath)\n\tif _, err := tmp.Write(data); err != nil {\n\t\ttmp.Close()\n\t\treturn fmt.Errorf(\"writing temp file: %w\", err)\n\t}\n\tif err := tmp.Close(); err != nil {\n\t\treturn fmt.Errorf(\"closing temp file: %w\", err)\n\t}\n\n\tmutex := fsutil.NewFileMutex(path + \".lock\")\n\tif err := mutex.Lock(); err != nil {\n\t\treturn fmt.Errorf(\"acquiring cache lock: %w\", err)\n\t}\n\tdefer func() { _ = mutex.Unlock() }()\n\n\t//nolint:forbidigo // We acquire a mutex to avoid concurrent writes\n\tif err := os.Rename(tmpPath, path); err != nil {\n\t\treturn fmt.Errorf(\"renaming temp file: %w\", err)\n\t}\n\treturn nil\n}\n\n// fetchSpec retrieves the OpenAPI document via Client.GetCloudAPISpec.\nfunc fetchSpec(ctx context.Context, resolved *ResolvedContext) ([]byte, error) {\n\tbody, err := resolved.Client.GetCloudAPISpec(ctx)","sourceCodeStart":124,"sourceCodeEnd":160,"githubUrl":"https://github.com/pulumi/pulumi/blob/793f7b2e160db4321fb7fb6b0607461e01cb251e/pkg/cmd/pulumi/cloud/spec_fetch.go#L124-L160","documentation":"writeCachedSpec closes the temp file before renaming it into place; failing close is wrapped as \"closing temp file\". Close is where buffered write errors surface and on some platforms can also report permission/fs errors. This is rare and usually accompanies a prior write problem.","triggerScenarios":"ensureSpec/seedSpecCache flushing the temp spec file when the filesystem reports errors on fsync/close: ENOSPC on metadata update, stale NFS file handle, or file already deleted underneath the handle.","commonSituations":"Network filesystems (NFS/EFS/SMB) invalidating handles mid-operation; disk filling between write and close; security agents interfering with file handles.","solutions":["Retry the command — transient network-filesystem errors often resolve.","Check disk space and quota on the cache volume.","Move the cache to local disk (XDG_CACHE_HOME or Pulumi cache config) to avoid NFS/SMB close errors.","If it persists, inspect system logs (dmesg / mount health) for I/O errors on the device."],"exampleFix":"// before\n# cache on flaky NFS mount\n// after\nexport XDG_CACHE_HOME=/local/tmp/cache && pulumi api ls","handlingStrategy":"retry","validationCode":"// avoid flaky network mounts for the cache\nif isNetworkMount(filepath.Dir(path)) {\n    os.Setenv(\"XDG_CACHE_HOME\", \"/tmp/pulumi-cache\")\n}","typeGuard":null,"tryCatchPattern":"if err := runAPICmd(); err != nil && strings.Contains(err.Error(), \"closing temp file\") {\n    time.Sleep(2 * time.Second)\n    return runAPICmd() // transient NFS/I-O errors often clear\n}","preventionTips":["Place the pulumi cache on local persistent storage, not NFS/SMB.","Retry transient I-O failures once before investigating.","Keep adequate disk headroom to avoid close-time ENOSPC."],"tags":["filesystem","cache","io"],"backgroundTag":"disk-full-enospc","analyzedSha":"793f7b2e160db4321fb7fb6b0607461e01cb251e","analyzedAt":"2026-08-31T09:36:43.099Z","schemaVersion":2},"datasetVersion":"2026-09-01T08:17:40.651Z"}