{"record":{"id":"9331996c8a46f92c","repo":"jdx/mise","slug":"ditto-failed-copying-to","errorCode":null,"errorMessage":"ditto failed copying {} to {}","messagePattern":"ditto failed copying (.+?) to (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/packages/brew/cask.rs","lineNumber":2641,"sourceCode":"            .ok_or_else(|| eyre!(\"brew-cask: generic artifact target has no filename\"))?;\n        nix::fcntl::renameat(&parent.fd, from_name, &parent.fd, to_name)?;\n        Ok(())\n    }\n    #[cfg(not(unix))]\n    {\n        let _ = expected_parent;\n        file::rename(from, to)\n    }\n}\n\nfn ditto(from: &Path, to: &Path) -> Result<()> {\n    let status = std::process::Command::new(\"ditto\")\n        .arg(from)\n        .arg(to)\n        .status()\n        .wrap_err(\"failed to run ditto\")?;\n    if !status.success() {\n        bail!(\n            \"ditto failed copying {} to {}\",\n            from.display(),\n            to.display()\n        );\n    }\n    Ok(())\n}\n\n/// Run a helper with its working directory bound to `dir` via `fchdir`, so\n/// relative arguments resolve from that exact directory inode.\n///\n/// Passing a pathname to a subprocess would let it re-resolve every component,\n/// which a same-uid replacement can redirect. `fchdir` in the child pins\n/// resolution to the descriptor mise already verified, so relative names cannot\n/// escape the validated application directory.\n#[cfg(unix)]\nfn run_in_trusted_dir<Fd: std::os::fd::AsFd>(\n    program: &str,","sourceCodeStart":2623,"sourceCodeEnd":2659,"githubUrl":"https://github.com/jdx/mise/blob/6f52dcdf99e282ef7a7db68c81301fa4618d0f79/src/system/packages/brew/cask.rs#L2623-L2659","documentation":"ditto shells out to the macOS /usr/bin/ditto tool to copy bundles. This bail fires when ditto ran but exited with a non-zero status (a spawn failure surfaces earlier, wrapped as \"failed to run ditto\"). Both the source and destination paths are included so the exact copy can be reproduced by hand to see ditto's own diagnostic.","triggerScenarios":"ditto exits non-zero: permission denied reading the source or writing the destination, destination inside a read-only or SIP-protected location, disk full, source bundle missing mid-copy, or metadata (xattr/ACL) errors on the destination filesystem; also when ditto is absent (non-macOS) as the spawn-error variant.","commonSituations":"Full disk on the volume holding the destination; staging areas on filesystems that reject macOS metadata (FAT/exFAT); quarantined or unreadable source bundles; attempting bundle-copying casks on Linux.","solutions":["Reproduce manually: `ditto <from> <to>` and read the real diagnostic","Free disk space and fix write permissions on the destination parent","Ensure the destination volume supports extended attributes — avoid FAT/exFAT for caskrooms/appdirs","On non-macOS, don't install casks whose artifacts require ditto bundle copies"],"exampleFix":"# before\n$ ditto /stage/Foo.app /Applications/Foo.app\nditto: ... Permission denied\n\n# after\n$ sudo chown -R \"$USER\":admin /Applications/Foo.app 2>/dev/null; rm -rf /Applications/Foo.app\n$ ditto /stage/Foo.app /Applications/Foo.app","handlingStrategy":"try-catch","validationCode":"// Pre-flight the copy the way ditto will do it.\nif cfg!(target_os = \"macos\") {\n    std::process::Command::new(\"ditto\").arg(\"--version\").status()?; // ditto present\n}\ncheck_writable(destination.parent().unwrap())?;\ncheck_free_space(destination, source_size)?;","typeGuard":null,"tryCatchPattern":"if let Err(err) = ditto(from, to) {\n    if err.to_string().starts_with(\"ditto failed copying\") {\n        // run `ditto <from> <to>` by hand to surface the real diagnostic,\n        // fix perms/space, then retry the install once\n    }\n}","preventionTips":["Keep the appdir/caskroom volumes off FAT/exFAT so xattr copies succeed","Monitor disk space before large bundle installs","Reproduce any ditto failure manually before changing configuration"],"tags":["brew-cask","ditto","macos","copy","subprocess"],"backgroundTag":"subprocess-nonzero-exit","analyzedSha":"6f52dcdf99e282ef7a7db68c81301fa4618d0f79","analyzedAt":"2026-08-22T10:14:23.840Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}