dagger/dagger · error

failed to change mode: %w

Error message

failed to change mode: %w

What it means

Returned by rewriteMetadata in engine/filesync/localfs.go when os.Chmod(p, mode) fails while restoring a synced file's permission bits (skipped for symlinks), and re-wrapped by WriteFile as 'failed to rewrite file metadata'. The file data is already written; the library aborts because the copy's permissions would otherwise diverge from the source.

Source

Thrown at engine/filesync/localfs.go:1066

	if err := verifyExpectedChange(path, appliedChange.result(), expectedChangeKind, upperStat); err != nil {
		appliedChange.release()
		return nil, 0, err
	}
	return appliedChange, writtenBytes, nil
}

func (local *localFS) Walk(ctx context.Context, path string, walkFn fs.WalkDirFunc) error {
	return local.filterFS.Walk(ctx, path, walkFn)
}

func rewriteMetadata(p string, upperStat *types.Stat) error {
	if err := os.Lchown(p, int(upperStat.Uid), int(upperStat.Gid)); err != nil {
		return fmt.Errorf("failed to change owner: %w", err)
	}

	if os.FileMode(upperStat.Mode)&os.ModeSymlink == 0 {
		if err := os.Chmod(p, os.FileMode(upperStat.Mode)); err != nil {
			return fmt.Errorf("failed to change mode: %w", err)
		}
	}

	var utimes [2]unix.Timespec
	utimes[0] = unix.NsecToTimespec(upperStat.ModTime)
	utimes[1] = utimes[0]

	if err := unix.UtimesNanoAt(unix.AT_FDCWD, p, utimes[0:], unix.AT_SYMLINK_NOFOLLOW); err != nil {
		return fmt.Errorf("failed to call utimes: %w", err)
	}

	return nil
}

// Check that the change applied by mutating methods is actually the one we thought we were applying. If not, the client
// filesystem changed during the sync and we need to error out to avoid inconsistencies.
func verifyExpectedChange(path string, appliedChange *ChangeWithStat, expectedKind ChangeKind, expectedStat *types.Stat) error {
	if appliedChange.kind == ChangeKindDelete || expectedKind == ChangeKindDelete {

View on GitHub (pinned to 82ba2681db)

Solutions

  1. Run as root or ensure the process retains ownership of the file so chmod is permitted (check ordering of chown/chmod when unprivileged)
  2. Clear immutable flags on the destination: chattr -i <file> (or remove the read-only mount option)
  3. Confirm the destination filesystem supports POSIX permissions; avoid FAT/exFAT targets or remount with proper support
  4. Check the wrapped errno in the error chain to distinguish EPERM (permissions) from EROFS (read-only fs) and fix accordingly
Defensive patterns

Strategy: try-catch

Validate before calling

// ensure the process owns the file it is about to chmod and fs is not read-only
info, err := os.Lstat(p)
if err != nil { return err }
if os.Geteuid() != 0 && info.Sys().(*syscall.Stat_t).Uid != uint32(os.Geteuid()) {
	return fmt.Errorf("process does not own %s; chmod will EPERM", p)
}
if err := unix.Access(dir, unix.W_OK); err != nil {
	return fmt.Errorf("destination not writable (read-only fs?): %w", err)
}

Try / catch

err := rewriteMetadata(p, upperStat)
var pathErr *os.PathError
if errors.As(err, &pathErr) && errors.Is(pathErr.Err, syscall.EPERM) {
	// un-chown first / run as root, or chattr -i the target, then retry
} else if errors.As(err, &pathErr) && errors.Is(pathErr.Err, syscall.EROFS) {
	// remount destination read-write
}

Prevention

When it happens

Trigger: os.Chmod on the freshly written file fails — typically EPERM because the process doesn't own the file (e.g. a preceding Lchown changed ownership to another user in a non-root context) or an immutable/read-only attribute is set; also possible on filesystems that don't support permission bits.

Common situations: Non-root flow where Lchown succeeded to a different owner making the process no longer the file owner (EPERM on chmod); files with chattr +i / immutable flag on the destination; FAT/exFAT or restricted NFS mounts; read-only bind mounts.

Related errors


AI-assisted analysis of dagger/dagger@82ba2681db (2026-09-05). Data as JSON: /api/errors/c70b1cac4ba02002. Report an issue: GitHub.