gastownhall/beads · error
failed to remove source after copy: %w
Error message
failed to remove source after copy: %w
What it means
moveFile falls back to copy-then-remove when a cross-filesystem rename fails with EXDEV. If the copy succeeds but removing the original source file fails, the error is wrapped as "failed to remove source after copy: %w" — the data now exists at BOTH src and dst, and the caller must know the move is only half-complete.
Source
Thrown at cmd/bd/doctor/fix/fs.go:26
"syscall"
)
var (
renameFile = os.Rename
removeFile = os.Remove
openFileRO = os.Open
openFileRW = os.OpenFile
)
func moveFile(src, dst string) error {
if err := renameFile(src, dst); err == nil {
return nil
} else if isEXDEV(err) {
if err := copyFile(src, dst); err != nil {
return err
}
if err := removeFile(src); err != nil {
return fmt.Errorf("failed to remove source after copy: %w", err)
}
return nil
} else {
return err
}
}
func copyFile(src, dst string) error {
in, err := openFileRO(src) // #nosec G304 -- src is within the workspace
if err != nil {
return err
}
defer in.Close()
out, err := openFileRW(dst, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
if err != nil {
return err
}
defer func() { _ = out.Close() }()View on GitHub (pinned to 71377f2769)
Solutions
- Delete the leftover source file manually once the destination is verified (rm <src>)
- Fix permissions on the source file/directory so it can be unlinked (need write on the parent dir)
- Verify the copied destination is intact before removing anything; if it is not, redo the move
Example fix
// before (source left behind)
err := fix.MoveFile("/mnt/a/db.x", "/mnt/b/db.x") // failed to remove source after copy: permission denied
// after: make source dir writable first
$ chmod u+w /mnt/a && rm /mnt/a/db.x Defensive patterns
Strategy: try-catch
Validate before calling
// Ensure the source can be removed before attempting a cross-device move
srcDir writable check: if f, err := os.OpenFile(filepath.Dir(src), os.O_WRONLY, 0); err != nil {
return fmt.Errorf("cannot unlink from %s: %w", filepath.Dir(src), err)
} else { f.Close() } Try / catch
if err := moveFile(src, dst); err != nil {
if strings.Contains(err.Error(), "failed to remove source after copy") {
// dst exists; clean up leftover src manually or retry removal
if rmErr := os.Remove(src); rmErr != nil {
return fmt.Errorf("both move cleanup attempts failed: %v; %w", rmErr, err)
}
return nil
}
return err
} Prevention
- Check write permission on the source file's parent directory before cross-device moves
- Don't move files concurrently being written by other processes
- After any EXDEV fallback, verify both src and dst state before proceeding
When it happens
Trigger: Calling moveFile (directly or via doctor filesystem fixes) across filesystems where os.Rename returns EXDEV, copyFile succeeds, then removeFile fails because the source file is read-only, its directory is not writable, the file was deleted/replaced concurrently, or the source mount is read-only.
Common situations: Moving database files between volumes with mismatched ownership; source directory on a read-only mount or immutable file; another process holding/rewriting the file during the doctor fix.
Related errors
- multiple .doltcfg directories detected
- dolt path is not executable
- server: NewDoltServer: failed to determine absolute path of
- server: NewDoltServer: failed to determine absolute path of
- server: NewDoltServer: failed to determine absolute path of
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/7c8599b204268226.
Report an issue: GitHub.