{"record":{"id":"d85b96ccc90f3937","repo":"gastownhall/beads","slug":"checking-sidecar-destination-s-w","errorCode":null,"errorMessage":"checking sidecar destination %s: %w","messagePattern":"checking sidecar destination (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/bd/migrate_hooks_apply.go","lineNumber":393,"sourceCode":"\t\t\t)\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc retireHookSidecar(op hookMigrationRetireOp) (string, error) {\n\tsourceExists, err := pathExists(op.SourcePath)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"checking sidecar %s: %w\", op.SourcePath, err)\n\t}\n\tif !sourceExists {\n\t\treturn \"\", nil\n\t}\n\n\tdestinationExists, err := pathExists(op.DestinationPath)\n\tif err != nil {\n\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)","sourceCodeStart":375,"sourceCodeEnd":411,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/cmd/bd/migrate_hooks_apply.go#L375-L411","documentation":"This wraps an os.Stat failure on the retire op's destination path during retirement (after the source was confirmed to exist). Any stat error other than a clean not-exist aborts the retire step. Because this happens during apply, it can leave the migration partially applied.","triggerScenarios":"During `bd migrate` apply, destinationExists check fails: destination parent directory permission-denied, destination on an unmounted volume, or an I/O error from the filesystem.","commonSituations":"Destination directory created with restrictive mode by an earlier run; external drive/NFS share disconnected between validation and apply; user switched effective uid via sudo mid-session.","solutions":["Inspect the wrapped error; run `ls -ld` on the destination path and its parents to find the failure point.","Fix directory permissions or remount the volume holding the destination.","Re-run `bd migrate`; retire ops skip sources that no longer exist, so retries are safe."],"exampleFix":"// before\n$ bd migrate\nchecking sidecar destination /mnt/ext/bd/x.sh: input/output error\n// after\n$ sudo mount /mnt/ext && bd migrate","handlingStrategy":"validation","validationCode":"if _, err := os.Stat(filepath.Dir(op.DestinationPath)); err != nil {\n\treturn fmt.Errorf(\"destination dir not accessible: %w\", err)\n}","typeGuard":null,"tryCatchPattern":"if _, err := retireHookSidecar(op); err != nil {\n\tvar pe *fs.PathError\n\tif errors.As(err, &pe) && !errors.Is(pe.Err, os.ErrNotExist) {\n\t\treturn fmt.Errorf(\"destination sidecar %s unusable: %w\", pe.Path, pe.Err)\n\t}\n\treturn err\n}","preventionTips":["Confirm the destination volume is mounted before migrating","Pre-create the destination directory with permissive-enough mode","Avoid switching uid (sudo) between preview and apply"],"tags":["filesystem","permissions","migration","go"],"backgroundTag":"permission-denied","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}