{"record":{"id":"8d6472aea98d20e2","repo":"gastownhall/beads","slug":"reason-file-q-is-empty-close-reason-is-require","errorCode":null,"errorMessage":"--reason-file %q is empty; close reason is required","messagePattern":"--reason-file %q is empty; close reason is required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/bd/close.go","lineNumber":684,"sourceCode":"// Returns (content, true, nil) when --reason-file was set and read successfully.\n// Returns (_, false, nil) when --reason-file was not set.\n// Returns an error on conflict with an existing reason, file read failure, or empty content.\n// Mirrors the --body-file pattern from `bd create` so agents can pass structured close\n// templates without shell-escaping hell.\nfunc resolveReasonFile(cmd *cobra.Command, hasExistingReason bool) (string, bool, error) {\n\tif !cmd.Flags().Changed(\"reason-file\") {\n\t\treturn \"\", false, nil\n\t}\n\tif hasExistingReason {\n\t\treturn \"\", false, fmt.Errorf(\"cannot specify both --reason-file and --reason/--resolution/--message/--comment\")\n\t}\n\tpath, _ := cmd.Flags().GetString(\"reason-file\")\n\tcontent, err := readBodyFile(path)\n\tif err != nil {\n\t\treturn \"\", false, fmt.Errorf(\"reading reason file %q: %w\", path, err)\n\t}\n\tif strings.TrimSpace(content) == \"\" {\n\t\treturn \"\", false, fmt.Errorf(\"--reason-file %q is empty; close reason is required\", path)\n\t}\n\treturn content, true, nil\n}\n\n// resolveCloseTargets resolves a batch of partial issue IDs for `bd close`,\n// preserving input order. For each ID it tries the local store first, then\n// explicit prefix routing via routes.jsonl, then a shared contributor-routed\n// store. This matches resolveAndGetIssueWithRouting's routing precedence.\n//\n// The contributor-routed handle is shared across the batch so bulk close does\n// not repeatedly open the same planning store and every result has a clear store\n// owner for subsequent close-time checks and writes.\n//\n// Each returned RoutedResult.Store points to whichever store actually owns the\n// issue. The caller invokes cleanup() once when done; per-result Close() is a\n// no-op for routed-via-shared-handle entries because they don't own the handle.\nfunc resolveCloseTargets(ctx context.Context, localStore storage.DoltStorage, ids []string) ([]*RoutedResult, func(), error) {\n\tresults := make([]*RoutedResult, 0, len(ids))","sourceCodeStart":666,"sourceCodeEnd":702,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/cmd/bd/close.go#L666-L702","documentation":"After successfully reading --reason-file, resolveReasonFile rejects content that is empty or whitespace-only, since a close reason is mandatory. This catches silent failures like an empty file or a file containing only newlines.","triggerScenarios":"--reason-file points to an existing but empty (or whitespace-only) file, including output of a failed pipeline redirected into the file.","commonSituations":"CI step writing the reason failed silently and produced an empty file; an editor saved a blank note; stdin redirected from an empty stream with `--reason-file -`.","solutions":["Populate the reason file with non-empty text and retry.","Check the upstream command that generated the file for silent failures.","Fall back to an inline --reason for this invocation."],"exampleFix":"// before\nbd close bd-1 --reason-file empty.txt\n// error: --reason-file \"empty.txt\" is empty\n// after\necho \"completed in sprint 12\" > reason.txt && bd close bd-1 --reason-file reason.txt","handlingStrategy":"validation","validationCode":"[ -s \"$REASON_FILE\" ] || { echo \"reason file is empty\" >&2; exit 1; }\n# -s fails on zero-size; also check whitespace:\ngrep -q '[^[:space:]]' \"$REASON_FILE\" || exit 1","typeGuard":null,"tryCatchPattern":"if err := runClose(...); err != nil {\n\tif strings.Contains(err.Error(), \"is empty; close reason is required\") {\n\t\t// regenerate the reason file or fall back to --reason\n\t}\n}","preventionTips":["Fail fast when generating reason files from pipelines","Use grep for non-whitespace, not just file size","Validate upstream command exit codes that produce the file"],"tags":["cli","validation","reason-file"],"backgroundTag":"empty-required-input","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}