gastownhall/beads · error
retiring sidecar %s -> %s: %w
Error message
retiring sidecar %s -> %s: %w
What it means
This wraps os.Rename failure when moving the source sidecar to the (previously nonexistent) destination during retirement. Rename fails on cross-device moves (EXDEV), missing source, or destination directory problems. The source file is left in place; migration aborts at this op.
Source
Thrown at cmd/bd/migrate_hooks_apply.go:411
return "", fmt.Errorf("checking sidecar destination %s: %w", op.DestinationPath, err)
}
if destinationExists {
equal, err := filesEqual(op.SourcePath, op.DestinationPath)
if err != nil {
return "", fmt.Errorf("comparing sidecar %s to %s: %w", op.SourcePath, op.DestinationPath, err)
}
if !equal {
return "", fmt.Errorf("artifact collision for %s: %s already exists with different content", op.SourcePath, op.DestinationPath)
}
if err := os.Remove(op.SourcePath); err != nil {
return "", fmt.Errorf("removing already-retired sidecar %s: %w", op.SourcePath, err)
}
return op.SourcePath + " -> " + op.DestinationPath + " (destination already existed)", nil
}
if err := os.Rename(op.SourcePath, op.DestinationPath); err != nil {
return "", fmt.Errorf("retiring sidecar %s -> %s: %w", op.SourcePath, op.DestinationPath, err)
}
return op.SourcePath + " -> " + op.DestinationPath, nil
}
func pathExists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
}
if errors.Is(err, os.ErrNotExist) {
return false, nil
}
return false, err
}
func filesEqual(pathA, pathB string) (bool, error) {
a, err := os.ReadFile(pathA) // #nosec G304 -- compared paths come from deterministic migration operationsView on GitHub (pinned to 71377f2769)
Solutions
- Check the wrapped error: 'invalid cross-device link' means the paths are on different filesystems — copy+remove manually or align mount points.
- If the source is missing, re-run `bd migrate` (the op is skipped) or restore the file.
- Ensure the destination parent directory is writable: chmod u+w <dest-dir>.
- As a fallback, `cp -a <src> <dst> && rm <src>` reproduces the rename's effect.
Example fix
// before rename /repo/hooks/x.sh /home/.local/.../x.sh: invalid cross-device link // after $ cp -a /repo/hooks/x.sh /home/.local/.../x.sh && rm /repo/hooks/x.sh
Defensive patterns
Strategy: fallback
Validate before calling
srcFi, _ := os.Stat(op.SourcePath)
dstFi, _ := os.Stat(filepath.Dir(op.DestinationPath))
if srcFi != nil && dstFi != nil && !os.SameFile(filepath.Dir(op.SourcePath), filepath.Dir(op.DestinationPath)) {
// different devices: use copy+remove instead of rename
} Try / catch
if err := os.Rename(src, dst); err != nil {
if errors.Is(err, syscall.EXDEV) {
data, rerr := os.ReadFile(src)
if rerr == nil && os.WriteFile(dst, data, 0o755) == nil {
os.Remove(src)
return nil
}
}
return err
} Prevention
- Keep source and destination sidecar paths on the same filesystem
- Ensure the destination parent directory exists and is writable before applying
- Don't delete source sidecars concurrently with a running migration
When it happens
Trigger: During `bd migrate` apply, os.Rename(op.SourcePath, op.DestinationPath) fails — source and destination are on different filesystems/mounts, the source vanished between the exists-check and rename, or the destination directory is not writable.
Common situations: Repo path and sidecar destination on different mounts (e.g. repo on NFS, hooks in ~/.local on tmpfs); another process deleted the source concurrently; destination parent directory permission-denied.
Related errors
- failed to rename temp file: %w
- failed to migrate database %s to %s: %w
- checking destination sidecar %s: %w
- comparing sidecars %s and %s: %w
- checking sidecar %s: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/2de99f46e1a34b50.
Report an issue: GitHub.