{"record":{"id":"8a8155b4339cffe4","repo":"larksuite/cli","slug":"file-changed-between-validation-and-open-8a8155","errorCode":null,"errorMessage":"file changed between validation and open","messagePattern":"file changed between validation and open","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/vfs/localfileio/openvalidated_windows.go","lineNumber":47,"sourceCode":"\tif err := inspectOpenedFile(f, pre); err != nil {\n\t\tf.Close()\n\t\t// An unusable target is a bad argument, not an internal fault: callers\n\t\t// map ErrPathValidation to a typed validation error, and the fd checks\n\t\t// are the same verdict the path checks make, one layer later.\n\t\treturn nil, &fileio.PathValidationError{Err: err}\n\t}\n\treturn f, nil\n}\n\n// inspectOpenedFile validates the opened handle. pre is nil when there is no\n// prior Stat to compare against.\nfunc inspectOpenedFile(f *os.File, pre os.FileInfo) error {\n\tpost, err := f.Stat()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"cannot stat opened file: %w\", err)\n\t}\n\tif pre != nil && !os.SameFile(pre, post) {\n\t\treturn fmt.Errorf(\"file changed between validation and open\")\n\t}\n\tif !post.Mode().IsRegular() {\n\t\treturn fmt.Errorf(\"not a regular file (directories, devices, FIFOs, and sockets are refused)\")\n\t}\n\tvar handleInfo syscall.ByHandleFileInformation\n\tif err := syscall.GetFileInformationByHandle(syscall.Handle(f.Fd()), &handleInfo); err != nil {\n\t\treturn fmt.Errorf(\"cannot inspect opened file links: %w\", err)\n\t}\n\tif handleInfo.NumberOfLinks > 1 {\n\t\treturn fmt.Errorf(\"file has multiple hard links, so the other names it can be reached by \" +\n\t\t\t\"cannot be checked (hint: copy the file and use the copy instead)\")\n\t}\n\treturn nil\n}\n","sourceCodeStart":29,"sourceCodeEnd":62,"githubUrl":"https://github.com/larksuite/cli/blob/7fd6ef3c07182257ce776cdc5a614e122d5bd4b3/internal/vfs/localfileio/openvalidated_windows.go#L29-L62","documentation":"The file's identity changed between the pre-open path validation and the opened handle: os.SameFile(pre, post) returned false. This library enforces a TOCTOU-safe open — the file you validated must be the file you got — so it refuses the handle instead of operating on a swapped file. Typically the path was replaced (delete+recreate, rename over) by another process in that window.","triggerScenarios":"Calling openValidated with a non-nil pre os.FileInfo on Windows when the path is deleted and recreated, renamed over, or its volume/device identity changes between the initial Stat and the successful open.","commonSituations":"Another process (editor save, build tool, sync client like Dropbox/OneDrive, deploy script) rewrites the file atomically via rename while you open it; symlink retargeting; config files regenerated in place by watchers.","solutions":["Re-run the whole validate+open cycle against the current file; if the swap was a one-off, the next attempt passes","Open the file exclusively or coordinate with the writer so the path is not replaced during open","Take your own fresh Stat immediately before open and pass it as pre, minimizing the race window","If replacement is expected, validate the new file's content instead of insisting on the old identity"],"exampleFix":"// before: stale pre-stat from long ago\npre := statTakenMinutesAgo\nf, err := openValidated(path, pre)\n// after: re-stat right before opening\npre, err := os.Stat(path)\nif err != nil { return err }\nf, err := openValidated(path, pre)","handlingStrategy":"validation","validationCode":"pre, err := os.Stat(path)\nif err != nil { return err }\n// pass pre immediately to openValidated; do not hold it across long operations","typeGuard":null,"tryCatchPattern":"f, err := openValidated(path, pre)\nif err != nil && strings.Contains(err.Error(), \"file changed between validation and open\") {\n    pre, serr := os.Stat(path)\n    if serr != nil { return serr }\n    f, err = openValidated(path, pre)\n}\nreturn err","preventionTips":["Re-stat immediately before opening; never cache os.FileInfo across long stretches","Coordinate with writers (watchers, sync clients) so the path is not rename-replaced during open","Prefer in-place writes by a single owner process over delete-and-recreate publishing"],"tags":["windows","filesystem","toctou","race-condition"],"backgroundTag":"file-changed-during-open","analyzedSha":"7fd6ef3c07182257ce776cdc5a614e122d5bd4b3","analyzedAt":"2026-09-04T21:17:44.649Z","contentChangedAt":"2026-09-04T21:17:44.649Z","schemaVersion":2},"datasetVersion":"2026-09-12T02:17:10.037Z"}