gastownhall/beads · error

cannot specify both --reason-file and --reason/--resolution/

Error message

cannot specify both --reason-file and --reason/--resolution/--message/--comment

What it means

resolveReasonFile refuses mutually exclusive input: if the --reason-file flag is set while a reason was already provided via --reason/--resolution/--message/--comment, close aborts. This prevents ambiguous close-reason sources.

Source

Thrown at cmd/bd/close.go:676

			return true
		}
	}

	return false
}

// 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

View on GitHub (pinned to 71377f2769)

Solutions

  1. Remove either the --reason-file flag or the conflicting --reason/--resolution/--message/--comment flag.
  2. Keep a single source of truth for the close reason in your scripts.

Example fix

// before
bd close bd-1 --reason "done" --reason-file note.txt
// after
bd close bd-1 --reason-file note.txt
Defensive patterns

Strategy: validation

Validate before calling

flags=()
[ -n "$REASON_FILE" ] && flags+=(--reason-file "$REASON_FILE")
[ -n "$REASON" ] && flags+=(--reason "$REASON")
[ ${#flags[@]} -gt 1 ] && echo 'use only one reason source' && exit 1

Type guard

func reasonSourcesConflict(reasonFile string, hasReason bool) bool {
	return reasonFile != "" && hasReason
}

Try / catch

if _, _, err := resolveReasonFile(cmd, hasReason); err != nil {
	if strings.Contains(err.Error(), "cannot specify both") {
		// drop one flag and retry
	}
}

Prevention

When it happens

Trigger: Invoking `bd close` with both --reason-file <path> and any of --reason, --resolution, --message, or --comment flags.

Common situations: Shell scripts adding --reason-file to an existing command line that already passes --reason; copy-pasted command templates merging two reason styles.

Related errors


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