gastownhall/beads · error
reading reason file %q: %w
Error message
reading reason file %q: %w
What it means
When --reason-file is given, resolveReasonFile reads it via readBodyFile and wraps any read failure with the offending path (`reading reason file %q: %w`). This surfaces filesystem-level problems (missing file, permission denied, read error) in the context of the close command.
Source
Thrown at cmd/bd/close.go:681
}
// resolveReasonFile resolves the --reason-file flag for `bd close`.
// Returns (content, true, nil) when --reason-file was set and read successfully.
// Returns (_, false, nil) when --reason-file was not set.
// Returns an error on conflict with an existing reason, file read failure, or empty content.
// Mirrors the --body-file pattern from `bd create` so agents can pass structured close
// templates without shell-escaping hell.
func resolveReasonFile(cmd *cobra.Command, hasExistingReason bool) (string, bool, error) {
if !cmd.Flags().Changed("reason-file") {
return "", false, nil
}
if hasExistingReason {
return "", false, fmt.Errorf("cannot specify both --reason-file and --reason/--resolution/--message/--comment")
}
path, _ := cmd.Flags().GetString("reason-file")
content, err := readBodyFile(path)
if err != nil {
return "", false, fmt.Errorf("reading reason file %q: %w", path, err)
}
if strings.TrimSpace(content) == "" {
return "", false, fmt.Errorf("--reason-file %q is empty; close reason is required", path)
}
return content, true, nil
}
// resolveCloseTargets resolves a batch of partial issue IDs for `bd close`,
// preserving input order. For each ID it tries the local store first, then
// explicit prefix routing via routes.jsonl, then a shared contributor-routed
// store. This matches resolveAndGetIssueWithRouting's routing precedence.
//
// The contributor-routed handle is shared across the batch so bulk close does
// not repeatedly open the same planning store and every result has a clear store
// owner for subsequent close-time checks and writes.
//
// Each returned RoutedResult.Store points to whichever store actually owns the
// issue. The caller invokes cleanup() once when done; per-result Close() is aView on GitHub (pinned to 71377f2769)
Solutions
- Verify the path exists and is readable (`cat <path>` or `test -r <path>`).
- Use an absolute path or correct the relative path.
- If '-' is intended to read stdin, pass --reason-file - explicitly.
Example fix
// before bd close bd-1 --reason-file ./notes.txt # file absent // after bd close bd-1 --reason-file /abs/path/notes.txt # ensure file exists first
Defensive patterns
Strategy: validation
Validate before calling
test -r "$REASON_FILE" || { echo "reason file missing: $REASON_FILE" >&2; exit 1; } Try / catch
if err := runClose(...); err != nil {
var wrapped string
if strings.Contains(err.Error(), "reading reason file") {
// check path existence/permissions, then retry
}
_ = wrapped
} Prevention
- Use absolute paths for reason files in scripts
- Create the reason file before invoking bd close
- Add existence checks in CI before close steps
When it happens
Trigger: --reason-file points to a nonexistent file, an unreadable path, or a directory; readBodyFile returns any I/O error.
Common situations: Typo in the file path; running from a different working directory with a relative path; file deleted between scripting steps; permission restrictions in CI.
Understand the failure class
Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.
Related errors
- --reason-file %q is empty; close reason is required
- target repo %s is not initialized; refusing to initialize it
- failed to inspect target repo %s: %w
- failed to get current directory: %v
- failed to create .beads directory: %v Windows Controlled Fo
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/41c1df57338b984d.
Report an issue: GitHub.