gastownhall/beads · error · issueops.ErrValidation

invalid group '%s'. Valid values: status, priority, type, as

Error message

invalid group '%s'. Valid values: status, priority, type, assignee, label%.0w

What it means

ValidateCountGroup refuses a grouping dimension outside the published set {status, priority, type, assignee, label}, wrapping the message with issueops.ErrValidation (via %.0w). Count queries group by exactly one recognized dimension; unknown names have no storage column mapping in countGroupColumns.

Source

Thrown at internal/workapi/count.go:29

// countGroupColumns is the closed set issueops.CountByGroupRequest.GroupBy
// promises, mapped to what the storage seam calls each dimension. It exists so
// an unknown value is refused HERE, as ErrValidation, rather than reaching
// storage and coming back as an unclassifiable "unsupported groupBy" string.
var countGroupColumns = map[issueops.CountGroup]string{
	issueops.CountGroupStatus:   "status",
	issueops.CountGroupPriority: "priority",
	issueops.CountGroupType:     "type",
	issueops.CountGroupAssignee: "assignee",
	issueops.CountGroupLabel:    "label",
}

// ValidateCountGroup resolves a grouping dimension to the name the storage
// seam takes, refusing anything outside the published set with ErrValidation.
func ValidateCountGroup(group issueops.CountGroup) (string, error) {
	column, ok := countGroupColumns[group]
	if !ok {
		return "", fmt.Errorf("invalid group '%s'. Valid values: status, priority, type, assignee, label%.0w",
			group, issueops.ErrValidation)
	}
	return column, nil
}

// BuildCountFilter turns a count request into the storage-level filter: the
// single definition of what `bd count` means, which every implementation of
// issueops.Counter builds through.
//
// It is a SEPARATE builder rather than a call into BuildListFilter with the
// paging fields blanked out, because the two commands disagree about the
// default answer: a listing hides closed, pinned, template and gate rows, and a
// count hides none of them. The one place they must agree is --include-infra,
// pinned by a golden-style test comparing this builder's output against
// BuildListFilter's for the same request (count_test.go, GH#4387).
//
// cfg supplies the workspace's infra vocabulary and is only read under
// IncludeInfra; a zero ListConfig falls back to the default infra set.

View on GitHub (pinned to 71377f2769)

Solutions

  1. Use one of the valid groups: status, priority, type, assignee, label
  2. Check the CountGroup constant definitions rather than casting raw strings
  3. If a new dimension is genuinely needed, file a feature request to add it to countGroupColumns

Example fix

// before
col, err := ValidateCountGroup(issueops.CountGroup("state"))
// after
col, err := ValidateCountGroup(issueops.CountGroup("status"))
Defensive patterns

Strategy: validation

Validate before calling

var validGroups = map[issueops.CountGroup]bool{
    "status": true, "priority": true, "type": true, "assignee": true, "label": true,
}
if !validGroups[g] { return fmt.Errorf("unsupported count group %q", g) }

Try / catch

col, err := workapi.ValidateCountGroup(g)
if errors.Is(err, issueops.ErrValidation) {
    return fmt.Errorf("%w (valid: status, priority, type, assignee, label)", err)
}

Prevention

When it happens

Trigger: Calling a count operation with issueops.CountGroup set to a value not present in countGroupColumns — e.g. a typo'd or newly invented group name passed to ValidateCountGroup.

Common situations: Misspelling a group name ("statues", "assigne"); using a plausible-but-unsupported dimension like "state" or "milestone"; older code constructing CountGroup values after the dimension set changed.

Related errors


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