{"record":{"id":"4f366bb3154d9d8b","repo":"gastownhall/beads","slug":"comparing-sidecar-s-to-s-w","errorCode":null,"errorMessage":"comparing sidecar %s to %s: %w","messagePattern":"comparing sidecar (.+?) to (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/bd/migrate_hooks_apply.go","lineNumber":399,"sourceCode":"\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)\n\t}\n\n\treturn op.SourcePath + \" -> \" + op.DestinationPath, nil\n}\n\nfunc pathExists(path string) (bool, error) {","sourceCodeStart":381,"sourceCodeEnd":417,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/cmd/bd/migrate_hooks_apply.go#L381-L417","documentation":"This wraps a failure from filesEqual when the destination sidecar already exists and retirement must verify the two are identical before deleting the source. A read error on either file aborts the retire step. This is an I/O problem, not a content conflict.","triggerScenarios":"During `bd migrate` apply, destination exists and os.ReadFile on the source or destination fails — file became unreadable after the exists-check (race), the path is actually a directory, or a disk read error occurs.","commonSituations":"Antivirus/backup tool locked or chmod'ed the file mid-run; files on flaky external storage; destination was swapped for a directory by another process.","solutions":["Check the wrapped error and `ls -la` both paths to confirm they are readable regular files.","Fix read permissions: chmod u+r on the unreadable file.","If the path is a directory or otherwise wrong, remove/relocate it manually and re-run.","Retry the migration after the interfering process (AV/backup) finishes."],"exampleFix":"// before\n-rw------- source-sidecar  (other user's file)\n// after\n$ chmod u+r /path/to/source-sidecar\n$ bd migrate","handlingStrategy":"validation","validationCode":"for _, p := range []string{op.SourcePath, op.DestinationPath} {\n\tf, err := os.Open(p)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"%s unreadable: %w\", p, err)\n\t}\n\tf.Close()\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, syscall.EACCES) {\n\t\t// grant read permission or stop interfering processes\n\t}\n\treturn err\n}","preventionTips":["Pause AV/backup/indexer jobs that lock hook files during migration","Keep sidecar files on local, healthy storage","Don't run concurrent bd migrations in the same repo"],"tags":["filesystem","io","migration","go"],"backgroundTag":"file-read-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}