dagger/dagger · error

failed to stat synctarget dest %s: %w

Error message

failed to stat synctarget dest %s: %w

What it means

In file/tar-stream mode, DiffCopy stats the destination path to decide how to write the file. If os.Stat fails for any reason other than 'does not exist', the situation is unrecoverable and the handler wraps the stat error with this message.

Source

Thrown at engine/client/filesync.go:292

	// an error when path is a pre-existing directory.
	allowParentDirPath := opts.AllowParentDirPath

	// File exports specifically (as opposed to container tar exports) have an original filename that we will
	// use in the case where dest is a directory and allowParentDirPath is set, in which case we need to know
	// what to name the file underneath the pre-existing directory.
	fileOriginalName := opts.FileOriginalName

	var destParentDir string
	var finalDestPath string
	stat, err := os.Stat(absPath)
	switch {
	case errors.Is(err, os.ErrNotExist):
		// we are writing the file to a new path
		destParentDir = filepath.Dir(absPath)
		finalDestPath = absPath
	case err != nil:
		// something went unrecoverably wrong if stat failed and it wasn't just because the path didn't exist
		return fmt.Errorf("failed to stat synctarget dest %s: %w", absPath, err)
	case !stat.IsDir():
		// we are overwriting an existing file
		destParentDir = filepath.Dir(absPath)
		finalDestPath = absPath
	case !allowParentDirPath:
		// we are writing to an existing directory, but allowParentDirPath is not set, so fail
		return fmt.Errorf("destination %q is a directory; must be a file path unless allowParentDirPath is set", absPath)
	default:
		// we are writing to an existing directory, and allowParentDirPath is set,
		// so write the file under the directory using the same file name as the source file
		if fileOriginalName == "" {
			// NOTE: we could instead just default to some name like container.tar or something if desired
			return fmt.Errorf("cannot export container tar to existing directory %q", absPath)
		}
		destParentDir = absPath
		finalDestPath = filepath.Join(destParentDir, fileOriginalName)
	}

View on GitHub (pinned to 82ba2681db)

Solutions

  1. Fix permissions so the sync user can traverse every parent directory of the destination
  2. Remove symlink loops or shorten the destination path
  3. Verify the parent path exists and the mount is healthy
  4. Export to a simpler, local, user-writable path to isolate the issue

Example fix

// before
await file.export("/mnt/nfs/very/deep/path/out.tar") // flaky NFS
// after
await file.export(filepath.Join(os.TempDir(), "out.tar"))
// then move into place yourself
Defensive patterns

Strategy: validation

Validate before calling

// walk every parent to catch EACCES/ELOOP before exporting
p := filepath.Dir(dest)
for {
    if _, err := os.Stat(p); err != nil {
        return fmt.Errorf("cannot stat %s: %w", p, err)
    }
    parent := filepath.Dir(p)
    if parent == p { break }
    p = parent
}

Type guard

func statable(path string) bool { _, err := os.Stat(path); return err == nil }

Prevention

When it happens

Trigger: os.Stat on the export destination fails with e.g. EACCES on a parent directory (cannot even check existence), ELOOP from a symlink cycle, ENAMETOOLONG, or I/O errors on the device.

Common situations: Destination nested under a directory the sync user cannot traverse; broken symlink loops after repeated exports; overly long paths from deep artifact trees (Windows MAX_PATH); failing network mounts.

Related errors


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