{"record":{"id":"89500d9a4fecafd5","repo":"dagger/dagger","slug":"touch-s-w","errorCode":null,"errorMessage":"touch %s: %w","messagePattern":"touch (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/changeset.go","lineNumber":1579,"sourceCode":"// touchAppliedPaths bumps the mtime of every path the changeset wrote so git\n// can see the change. Snapshot contents carry normalized timestamps and the\n// copier preserves them, so a same-size edit can leave a file's mtime and\n// size both identical to the index entry; with core.checkStat=minimal (see\n// gitEphemeralConfig) git add would then skip re-hashing it and silently\n// drop the change from the branch commit.\nfunc (ws *gitMergeWorkspace) touchAppliedPaths(paths *ChangesetPaths) error {\n\tfor _, p := range slices.Concat(paths.Added, paths.Modified) {\n\t\tif strings.HasSuffix(p, \"/\") {\n\t\t\t// Directories are untracked by git; only file stat data matters.\n\t\t\tcontinue\n\t\t}\n\t\tfull, err := RootPathWithoutFinalSymlink(ws.root, path.Join(ws.dir, p))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\terr = unix.UtimesNanoAt(unix.AT_FDCWD, full, nil, unix.AT_SYMLINK_NOFOLLOW)\n\t\tif err != nil && !errors.Is(err, unix.ENOENT) {\n\t\t\treturn fmt.Errorf(\"touch %s: %w\", p, err)\n\t\t}\n\t}\n\treturn nil\n}\n\n// verifyMergedPaths confirms that every file-level path the given changesets\n// declared as added or modified exists in the merged worktree. A conflict-free\n// merge has no legitimate way to drop one; a missing path means the temporary\n// repository lost applied content (a lost path here once meant a stale\n// .git/HEAD from a changeset diff had redirected the ours commit onto the\n// wrong branch, letting the merge silently resolve to one side).\nfunc (ws *gitMergeWorkspace) verifyMergedPaths(contents ...*changesetContent) error {\n\tvar missing []string\n\tfor _, content := range contents {\n\t\tif content == nil {\n\t\t\tcontinue\n\t\t}\n\t\tpaths := content.paths.withoutGitMeta()","sourceCodeStart":1561,"sourceCodeEnd":1597,"githubUrl":"https://github.com/dagger/dagger/blob/82ba2681dbe30d3547a1dc50ea495900ab5b6047/core/changeset.go#L1561-L1597","documentation":"touchAppliedPaths bumps mtimes of every added/modified file via unix.UtimesNanoAt so git's index stat check notices same-size edits; a failed utimensat (other than ENOENT, which is tolerated) is wrapped as 'touch <path>: <cause>'. Without this, git add with core.checkStat=minimal could silently skip re-hashing an edited file and drop the change from the merge commit.","triggerScenarios":"unix.UtimesNanoAt fails on a path the changeset wrote — typically EACCES/EPERM (no ownership of the file after copy), EROFS on the mount, EINVAL/EBADF on unusual file types, or ENOSPC-type metadata failures. ENOENT is explicitly ignored, so this error means the file exists but its timestamp cannot be set.","commonSituations":"Copied files owned by a different uid in the merge work tree; rootless/userns setups restricting utimensat on files the process does not own; read-only upperdir; exotic file types (sockets/devices) in the diff that reject utimensat.","solutions":["Check the wrapped errno in 'touch <path>: ...'; EPERM usually means ownership mismatch after copy","Run the engine/merge as a user with permission to set timestamps on the work tree files, or normalize ownership","Ensure the snapshot storage/overlay is writable (not EROFS)","Exclude special file types from changesets if EINVAL on sockets/devices","Retry the merge; the workspace is rebuilt each run"],"exampleFix":"// before\nerr = unix.UtimesNanoAt(unix.AT_FDCWD, full, nil, unix.AT_SYMLINK_NOFOLLOW)\nif err != nil && !errors.Is(err, unix.ENOENT) {\n\treturn fmt.Errorf(\"touch %s: %w\", p, err)\n}\n// after (caller-side workaround: chmod/chown the work tree so utimensat is permitted)\nif err := os.Chown(workTreeRoot, currentUID, currentGID); err != nil {\n\treturn fmt.Errorf(\"normalize work tree ownership: %w\", err)\n}\n// then retry the merge operation","handlingStrategy":"try-catch","validationCode":"// verify the merge work tree location is writable and ownership is consistent\ninfo, err := os.Stat(workTreeMountPoint)\nif err != nil || !isWritableByCurrentUser(info) {\n\treturn fmt.Errorf(\"merge work tree not writable/owned: %w\", err)\n}","typeGuard":"func isTouchError(err error) (string, bool) {\n\tif err == nil {\n\t\treturn \"\", false\n\t}\n\tpath, ok := strings.CutPrefix(err.Error(), \"touch \")\n\treturn strings.TrimSpace(path), ok\n}","tryCatchPattern":"err := dag.Directory().Merge(...)\nif err != nil {\n\tif path, ok := isTouchError(err); ok && errors.Is(err, os.ErrPermission) {\n\t\t// normalize ownership/permissions of engine storage, then retry\n\t\treturn fixOwnershipAndRetry(ctx, path)\n\t}\n\treturn err\n}","preventionTips":["Run the engine in a mode where copied files keep the current uid (or run rootless-consistently)","Ensure snapshot storage is on a writable filesystem","Avoid changesets containing sockets/devices and other utimensat-incompatible file types","Retry merges; the workspace is rebuilt from the base each time"],"tags":["filesystem","mtime","git-merge","utimensat"],"backgroundTag":"utimensat-permission-denied","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"}