gastownhall/beads · error

failed to read %s: %w

Error message

failed to read %s: %w

What it means

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.

Source

Thrown at cmd/bd/hooks.go:933

	for _, hookName := range hookNames {
		if err := guardHookWritePath(filepath.Join(hooksDir, hookName), shared); err != nil {
			return fmt.Errorf("refusing to install %s hook: %w", hookName, err)
		}
	}

	// Install each hook using section markers (GH#1380).
	// Only the content between markers is managed by beads; user content
	// outside the markers is preserved across reinstalls and upgrades.
	for _, hookName := range hookNames {
		hookPath := filepath.Join(hooksDir, hookName)
		section := generateHookSection(hookName)

		// Read existing hook file (if any)
		// #nosec G304 -- hook path constrained to hooks directory
		existing, readErr := os.ReadFile(hookPath)

		if readErr != nil && !os.IsNotExist(readErr) {
			return fmt.Errorf("failed to read %s: %w", hookName, readErr)
		}

		var newContent string
		if os.IsNotExist(readErr) {
			// No existing file — create with shebang + section
			newContent = "#!/usr/bin/env sh\n" + section
		} else {
			existingStr := string(existing)
			// Check if file already has section markers
			if strings.Contains(existingStr, hookSectionBeginPrefix) {
				// Update only the section between markers
				newContent = injectHookSection(existingStr, section)
			} else {
				// Check if this is a legacy bd hook (shim or inline)
				versionInfo, _ := getHookVersion(hookPath)
				if versionInfo.IsBdHook {
					// Legacy bd hook — replace entire file with section format
					newContent = "#!/usr/bin/env sh\n" + section

View on GitHub (pinned to 71377f2769)

Solutions

  1. Check the wrapped OS error (permission denied vs is-a-directory)
  2. Fix permissions: `chmod u+r <hooksDir>/<hook>` or re-own with chown
  3. If a directory occupies the hook name, remove it (`rm -rf .git/hooks/<name>`) and re-run
  4. Re-run `bd hooks install` after fixing

Example fix

// before
$ ls -l .git/hooks/pre-commit  # owned by root, mode 0600
// after
$ sudo chown $(whoami) .git/hooks/pre-commit && bd hooks install
Defensive patterns

Strategy: validation

Validate before calling

p="$hooksDir/pre-commit"; if [ -d "$p" ]; then echo "$p is a directory, not a file"; fi; [ -r "$p" ] || echo "$p not readable"

Try / catch

if err := installHooks(...); err != nil { if strings.Contains(err.Error(), "failed to read") { /* fix perms/special file, then retry */ } }

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/664ad572959c7c94. Report an issue: GitHub.