gastownhall/beads · error

invalid sort policy '%s'. Valid values: hybrid, priority, ol

Error message

invalid sort policy '%s'. Valid values: hybrid, priority, oldest

What it means

BuildReadyFilter rejects an unrecognized --sort policy with 'invalid sort policy ... Valid values: hybrid, priority, oldest', wrapping issueops.ErrValidation with %.0w so the message prints verbatim behind 'Error: ' without a prefix. The promise is that callers classify it with errors.Is rather than string matching.

Source

Thrown at internal/workapi/ready.go:100

	}
	if len(in.MetadataFields) > 0 {
		filter.MetadataFields = in.MetadataFields
	}

	if err := ValidateMetadataFilters(in.MetadataFields, in.HasMetadataKey); err != nil {
		return filter, err
	}

	if !filter.SortPolicy.IsValid() {
		// A deterministic request-validation failure, so it matches
		// ErrValidation: every role whose filter vocabulary this builds —
		// Reader.Ready, ReadyClaimer.ClaimNext and BatchCloser's ClaimNext —
		// promises a caller can classify one with errors.Is rather than by
		// reading prose. The wrap is %.0w rather than a "%w: " prefix because
		// this text is what `bd ready --sort bogus` prints verbatim behind
		// "Error: "; prefixing it would change user-visible copy to say
		// something the reader already knows.
		return filter, fmt.Errorf("invalid sort policy '%s'. Valid values: hybrid, priority, oldest%.0w", in.Sort, issueops.ErrValidation)
	}
	return filter, nil
}

// BuildReadyCountFilter turns a ready request into the storage-level filter a
// COUNT of the ready set runs against: BuildReadyFilter's filter with the page
// removed, and the single definition of what `bd ready`'s published total means.
//
// It refuses a request carrying a page. issueops.ReadyCounter.CountReady
// promises its answer equals len(Reader.Ready(r with Limit=0).Items), and a
// Limit would make that "how many of the first N" while an Offset would
// subtract the rows it skipped from the size of a set that still holds them.
//
// The zeroed limit is set on a LOCAL copy: a nil Limit means the shared ready
// default at BuildReadyFilter, so an unlimited count has to say so explicitly.
func BuildReadyCountFilter(in issueops.ReadyRequest) (types.WorkFilter, error) {
	if in.Limit != nil {
		return types.WorkFilter{}, fmt.Errorf("%w: a ready count does not take a limit", issueops.ErrValidation)

View on GitHub (pinned to 71377f2769)

Solutions

  1. Use exactly one of hybrid, priority, or oldest (or leave empty for the default)
  2. Check errors.Is(err, issueops.ErrValidation) to classify the refusal programmatically
  3. If a new policy is genuinely needed, file/extend the filter rather than passing arbitrary strings

Example fix

// before
bd ready --sort newest
// after
bd ready --sort oldest
Defensive patterns

Strategy: validation

Validate before calling

var validSorts = map[string]bool{"hybrid": true, "priority": true, "oldest": true}
if in.Sort != "" && !validSorts[in.Sort] {
	return fmt.Errorf("sort must be one of hybrid, priority, oldest")
}

Try / catch

filter, err := BuildReadyFilter(req)
if err != nil && errors.Is(err, issueops.ErrValidation) && strings.Contains(err.Error(), "invalid sort policy") {
	// reject the flag value; message is user-facing verbatim
}

Prevention

When it happens

Trigger: Calling BuildReadyFilter with issueops.ReadyRequest.Sort set to anything other than "", "hybrid", "priority", or "oldest" — e.g. `bd ready --sort bogus`.

Common situations: Typo'd or abbreviated sort names ('prio', 'hyb'); scripts written against an older sort vocabulary; config files carrying a sort value from a fork with extra policies.

Understand the failure class

Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.

Related errors


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