{"record":{"id":"664ad572959c7c94","repo":"gastownhall/beads","slug":"failed-to-read-s-w","errorCode":null,"errorMessage":"failed to read %s: %w","messagePattern":"failed to read (.+?): %w","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/bd/hooks.go","lineNumber":933,"sourceCode":"\tfor _, hookName := range hookNames {\n\t\tif err := guardHookWritePath(filepath.Join(hooksDir, hookName), shared); err != nil {\n\t\t\treturn fmt.Errorf(\"refusing to install %s hook: %w\", hookName, err)\n\t\t}\n\t}\n\n\t// Install each hook using section markers (GH#1380).\n\t// Only the content between markers is managed by beads; user content\n\t// outside the markers is preserved across reinstalls and upgrades.\n\tfor _, hookName := range hookNames {\n\t\thookPath := filepath.Join(hooksDir, hookName)\n\t\tsection := generateHookSection(hookName)\n\n\t\t// Read existing hook file (if any)\n\t\t// #nosec G304 -- hook path constrained to hooks directory\n\t\texisting, readErr := os.ReadFile(hookPath)\n\n\t\tif readErr != nil && !os.IsNotExist(readErr) {\n\t\t\treturn fmt.Errorf(\"failed to read %s: %w\", hookName, readErr)\n\t\t}\n\n\t\tvar newContent string\n\t\tif os.IsNotExist(readErr) {\n\t\t\t// No existing file — create with shebang + section\n\t\t\tnewContent = \"#!/usr/bin/env sh\\n\" + section\n\t\t} else {\n\t\t\texistingStr := string(existing)\n\t\t\t// Check if file already has section markers\n\t\t\tif strings.Contains(existingStr, hookSectionBeginPrefix) {\n\t\t\t\t// Update only the section between markers\n\t\t\t\tnewContent = injectHookSection(existingStr, section)\n\t\t\t} else {\n\t\t\t\t// Check if this is a legacy bd hook (shim or inline)\n\t\t\t\tversionInfo, _ := getHookVersion(hookPath)\n\t\t\t\tif versionInfo.IsBdHook {\n\t\t\t\t\t// Legacy bd hook — replace entire file with section format\n\t\t\t\t\tnewContent = \"#!/usr/bin/env sh\\n\" + section","sourceCodeStart":915,"sourceCodeEnd":951,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/cmd/bd/hooks.go#L915-L951","documentation":"Before merging its section, bd reads the existing hook file with os.ReadFile; if the read fails for any reason other than not-exist, installation aborts with this wrapped error. Missing files are handled normally (a fresh hook is created), so this indicates the path exists but is unreadable or is a special/dangling file.","triggerScenarios":"installHooksWithOptions reads hookPath and gets an error that is not os.IsNotExist — permission denied, path is a directory, I/O error on a dangling/special file.","commonSituations":"Hook file owned by root while bd runs as another user; a directory named `pre-commit` left in the hooks dir; corrupted filesystem; unusual ACLs.","solutions":["Check the wrapped OS error (permission denied vs is-a-directory)","Fix permissions: `chmod u+r <hooksDir>/<hook>` or re-own with chown","If a directory occupies the hook name, remove it (`rm -rf .git/hooks/<name>`) and re-run","Re-run `bd hooks install` after fixing"],"exampleFix":"// before\n$ ls -l .git/hooks/pre-commit  # owned by root, mode 0600\n// after\n$ sudo chown $(whoami) .git/hooks/pre-commit && bd hooks install","handlingStrategy":"validation","validationCode":"p=\"$hooksDir/pre-commit\"; if [ -d \"$p\" ]; then echo \"$p is a directory, not a file\"; fi; [ -r \"$p\" ] || echo \"$p not readable\"","typeGuard":null,"tryCatchPattern":"if err := installHooks(...); err != nil { if strings.Contains(err.Error(), \"failed to read\") { /* fix perms/special file, then retry */ } }","preventionTips":["Keep hook paths as plain readable files","Avoid running bd as a different user than the repo owner","Check for stray directories named like hooks in the hooks dir"],"tags":["filesystem","permissions","hooks"],"backgroundTag":"file-read-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}