{"record":{"id":"baf7d193791b660b","repo":"dagger/dagger","slug":"failed-to-stat-srcpath-from-differ","errorCode":null,"errorMessage":"failed to stat srcPath from differ","messagePattern":"failed to stat srcPath from differ","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"engine/snapshots/diffapply_linux.go","lineNumber":620,"sourceCode":"\t\t\t\t\t\tc.srcPath = path\n\t\t\t\t\t\tsrcfi = stat\n\t\t\t\t\t\tbreak\n\t\t\t\t\t} else if errors.Is(err, unix.ENOENT) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn errors.Wrap(err, \"failed to lstat when finding direct path of overlay file\")\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\tdefault:\n\t\t\t\tsrcPath, err := safeJoin(d.upperRoot, subPath)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn errors.Wrapf(err, \"failed to join %s and %s\", d.upperRoot, subPath)\n\t\t\t\t}\n\t\t\t\tc.srcPath = srcPath\n\t\t\t\tif fi, err := os.Lstat(c.srcPath); err == nil {\n\t\t\t\t\tsrcfi = fi\n\t\t\t\t} else {\n\t\t\t\t\treturn errors.Wrap(err, \"failed to stat srcPath from differ\")\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tvar ok bool\n\t\t\tc.srcStat, ok = srcfi.Sys().(*syscall.Stat_t)\n\t\t\tif !ok {\n\t\t\t\treturn errors.Errorf(\"unhandled stat type for %+v\", srcfi)\n\t\t\t}\n\n\t\t\tif !srcfi.IsDir() && c.srcStat.Nlink > 1 {\n\t\t\t\tif linkSubPath, ok := d.inodes[statInode(c.srcStat)]; ok {\n\t\t\t\t\tc.linkSubPath = linkSubPath\n\t\t\t\t} else {\n\t\t\t\t\td.inodes[statInode(c.srcStat)] = c.subPath\n\t\t\t\t}\n\t\t\t}\n\t\t}\n","sourceCodeStart":602,"sourceCodeEnd":638,"githubUrl":"https://github.com/dagger/dagger/blob/82ba2681dbe30d3547a1dc50ea495900ab5b6047/engine/snapshots/diffapply_linux.go#L602-L638","documentation":"After joining the upper root with the change's subPath, the differ calls os.Lstat on the resolved source path; failure here is wrapped with this message. The walk produced a change for a path that no longer exists (or is unstatable) in the upper root, so the diff cannot capture the file's metadata.","triggerScenarios":"os.Lstat(c.srcPath) fails in the default branch of doubleWalkingChanges: the file was deleted between the lower/upper walk and this stat (race), the upper root changed underneath, or permissions block the stat.","commonSituations":"Concurrent builds mutating the same snapshot; container teardown racing an image export; running the engine against a snapshot on storage another process is cleaning; permission mismatch after UID change.","solutions":["Retry the diff/export — mid-walk deletion races are usually transient.","Hold the snapshot exclusively (no concurrent writes/unmounts) while HandleChanges runs.","Check the wrapped errno: fix EACCES with corrected permissions on the upper root path.","If reproducible, inspect the specific subPath for leftover whiteouts or broken entries and clean the snapshot."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"if _, err := os.Lstat(filepath.Join(upperRoot, subPath)); err != nil {\n    return fmt.Errorf(\"change target %s missing from upper root: %w\", subPath, err)\n}","typeGuard":null,"tryCatchPattern":"err := differ.HandleChanges(ctx, handle)\nvar pe *os.PathError\nif errors.As(err, &pe) && errors.Is(pe.Err, unix.ENOENT) {\n    time.Sleep(50 * time.Millisecond)\n    err = differ.HandleChanges(ctx, handle)\n}\nreturn err","preventionTips":["Serialize exports so no concurrent write/delete races the differ","Keep the container/snapshot alive until HandleChanges returns","Retry once on ENOENT-class failures","Check permissions on upperRoot if errno is EACCES rather than ENOENT"],"tags":["filesystem","race-condition","diff","linux","snapshot"],"backgroundTag":"enoent-during-diff-walk","analyzedSha":"82ba2681dbe30d3547a1dc50ea495900ab5b6047","analyzedAt":"2026-09-05T07:21:37.930Z","contentChangedAt":"2026-09-05T07:21:37.930Z","schemaVersion":2},"datasetVersion":"2026-09-12T12:17:11.808Z"}