gastownhall/beads · error

got %d close reasons for %d issue IDs; provide exactly one s

Error message

got %d close reasons for %d issue IDs; provide exactly one shared reason or one reason per issue

What it means

resolveCloseReasons validates that when multiple `--reason` flags are supplied to `bd close`, their count must either be 1 (shared reason for all issues) or exactly equal to the number of issue ID arguments. If the counts mismatch, closing is refused to avoid ambiguous reason-to-issue mapping.

Source

Thrown at cmd/bd/close.go:452

	if fileReason, ok, err := resolveReasonFile(cmd, len(reasons) > 0); err != nil {
		return nil, args, err
	} else if ok {
		reasons = []string{fileReason}
	}

	// Desire-path: "bd done <id> <message>" treats last positional arg as reason
	// when no reason flag was explicitly provided (hq-pe8ce)
	if len(reasons) == 0 && cmd.CalledAs() == "done" && len(args) >= 2 {
		reasons = []string{args[len(args)-1]}
		args = args[:len(args)-1]
	}

	if len(reasons) == 0 {
		reasons = []string{"Closed"}
	}
	if len(reasons) > 1 && len(reasons) != len(args) {
		return nil, args, fmt.Errorf("got %d close reasons for %d issue IDs; provide exactly one shared reason or one reason per issue", len(reasons), len(args))
	}
	return reasons, args, nil
}

func collectCloseReasonFlags(cmd *cobra.Command) ([]string, error) {
	if flag := cmd.Flags().Lookup("reason"); flag != nil {
		if v, ok := flag.Value.(interface{ Values() []string }); ok {
			if reasons := nonEmptyCloseReasons(v.Values()); len(reasons) > 0 {
				return reasons, nil
			}
		}
	}

	for _, name := range []string{"resolution", "message", "comment"} {
		reason, err := cmd.Flags().GetString(name)
		if err != nil {
			return nil, err
		}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Supply exactly one --reason to apply it to all issues, or one --reason per issue ID so counts match.
  2. Count reasons and IDs in your script before invoking bd close.
  3. Alternatively use --reason-file with a single shared reason text.

Example fix

// before
bd close bd-1 bd-2 bd-3 --reason "done" --reason "done"
// after
bd close bd-1 bd-2 bd-3 --reason "done"
Defensive patterns

Strategy: validation

Validate before calling

if [ $(printf '%s\n' "$@" | grep -c -- '--reason') -gt 1 ]; then
  # ensure reasons count == issue ids count, else use a single --reason
fi

Type guard

func reasonsMatchIDs(reasons, ids []string) bool {
	return len(reasons) <= 1 || len(reasons) == len(ids)
}

Try / catch

reasons, args, err := resolveCloseReasons(cmd, args)
if err != nil && strings.Contains(err.Error(), "close reasons for") {
	// fall back to a single shared reason and retry
}

Prevention

When it happens

Trigger: Calling `bd close` with N issue IDs and more than one --reason flag where the number of reasons != N, e.g. `bd close bd-1 bd-2 bd-3 --reason a --reason b`.

Common situations: Shell scripts building the reason list dynamically; users pasting a partial list of per-issue reasons; forgetting an issue ID in the argument list while providing full per-issue reasons.

Related errors


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