{"record":{"id":"2de99f46e1a34b50","repo":"gastownhall/beads","slug":"retiring-sidecar-s-s-w","errorCode":null,"errorMessage":"retiring sidecar %s -> %s: %w","messagePattern":"retiring sidecar (.+?) -> (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/bd/migrate_hooks_apply.go","lineNumber":411,"sourceCode":"\t\treturn \"\", fmt.Errorf(\"checking sidecar destination %s: %w\", op.DestinationPath, err)\n\t}\n\n\tif destinationExists {\n\t\tequal, err := filesEqual(op.SourcePath, op.DestinationPath)\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"comparing sidecar %s to %s: %w\", op.SourcePath, op.DestinationPath, err)\n\t\t}\n\t\tif !equal {\n\t\t\treturn \"\", fmt.Errorf(\"artifact collision for %s: %s already exists with different content\", op.SourcePath, op.DestinationPath)\n\t\t}\n\t\tif err := os.Remove(op.SourcePath); err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"removing already-retired sidecar %s: %w\", op.SourcePath, err)\n\t\t}\n\t\treturn op.SourcePath + \" -> \" + op.DestinationPath + \" (destination already existed)\", nil\n\t}\n\n\tif err := os.Rename(op.SourcePath, op.DestinationPath); err != nil {\n\t\treturn \"\", fmt.Errorf(\"retiring sidecar %s -> %s: %w\", op.SourcePath, op.DestinationPath, err)\n\t}\n\n\treturn op.SourcePath + \" -> \" + op.DestinationPath, nil\n}\n\nfunc pathExists(path string) (bool, error) {\n\t_, err := os.Stat(path)\n\tif err == nil {\n\t\treturn true, nil\n\t}\n\tif errors.Is(err, os.ErrNotExist) {\n\t\treturn false, nil\n\t}\n\treturn false, err\n}\n\nfunc filesEqual(pathA, pathB string) (bool, error) {\n\ta, err := os.ReadFile(pathA) // #nosec G304 -- compared paths come from deterministic migration operations","sourceCodeStart":393,"sourceCodeEnd":429,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/cmd/bd/migrate_hooks_apply.go#L393-L429","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nrename /repo/hooks/x.sh /home/.local/.../x.sh: invalid cross-device link\n// after\n$ cp -a /repo/hooks/x.sh /home/.local/.../x.sh && rm /repo/hooks/x.sh","handlingStrategy":"fallback","validationCode":"srcFi, _ := os.Stat(op.SourcePath)\ndstFi, _ := os.Stat(filepath.Dir(op.DestinationPath))\nif srcFi != nil && dstFi != nil && !os.SameFile(filepath.Dir(op.SourcePath), filepath.Dir(op.DestinationPath)) {\n\t// different devices: use copy+remove instead of rename\n}","typeGuard":null,"tryCatchPattern":"if err := os.Rename(src, dst); err != nil {\n\tif errors.Is(err, syscall.EXDEV) {\n\t\tdata, rerr := os.ReadFile(src)\n\t\tif rerr == nil && os.WriteFile(dst, data, 0o755) == nil {\n\t\t\tos.Remove(src)\n\t\t\treturn nil\n\t\t}\n\t}\n\treturn err\n}","preventionTips":["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"],"tags":["filesystem","rename","migration","go"],"backgroundTag":"cross-device-rename","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}