gastownhall/beads · error
a ready count does not take a limit
Error message
a ready count does not take a limit
What it means
BuildReadyCountFilter refuses a ReadyRequest that carries a Limit, because a COUNT of the ready set must be unbounded — any limit would make the count answer 'how many up to N' rather than the set size. The refusal wraps issueops.ErrValidation with the message as a %w suffix.
Source
Thrown at internal/workapi/ready.go:118
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)
}
if in.Offset != 0 {
return types.WorkFilter{}, fmt.Errorf("%w: a ready count does not take an offset", issueops.ErrValidation)
}
unlimited := 0
counted := in
counted.Limit = &unlimited
// Brief is CARRIED, not refused and not cleared, which is the opposite of
// what ClaimNext does with it. A count reads no field of any row, so the
// projection cannot make the number wrong; and the count is not always
// cheap enough for that to be the end of it. The unit-of-work seam has no
// COUNT(*) over the ready predicate and sizes the set by running the
// unbounded page and taking its length (uow/ready_counter.go), so clearing
// the field here would hydrate every heavy column of the whole ready set to
// answer `bd ready --brief`, which is the cost the projection exists to
// avoid and larger than the page it was asked for. Carrying it also keeps
// the count filter what this builder says it is: the listing's filter with
// the PAGE removed, and nothing else removed.View on GitHub (pinned to 71377f2769)
Solutions
- Nil out Limit on the request before calling BuildReadyCountFilter
- Build a separate ReadyRequest for counting with no Limit set
- At the handler layer, ignore/reject limit params for the count route
Example fix
// before req.Limit = &pageLimit countFilter, err := BuildReadyCountFilter(req) // after req.Limit = nil countFilter, err := BuildReadyCountFilter(req)
Defensive patterns
Strategy: validation
Validate before calling
if req.Limit != nil {
return errors.New("count requests must not set a limit")
} Try / catch
filter, err := BuildReadyCountFilter(req)
if err != nil && errors.Is(err, issueops.ErrValidation) && strings.Contains(err.Error(), "does not take a limit") {
// clear Limit and rebuild
} Prevention
- Build count requests separately from paged listing requests
- Clear Limit (set nil) when reusing a ReadyRequest for counting
- Reject limit query params on count endpoints at the handler layer
When it happens
Trigger: Calling BuildReadyCountFilter with in.Limit != nil, e.g. a client reusing the same ReadyRequest it built for a paged `bd ready` call for the count endpoint.
Common situations: Shared request builders feeding both the list and count paths; an HTTP handler binding the page limit from the query string into the count request; copying a ReadyRequest struct wholesale without clearing Limit.
Related errors
- a ready count does not take an offset
- count by id: sourceID must not be empty
- %w: claim next does not take a limit
- %w: claim next does not take an offset
- invalid group '%s'. Valid values: status, priority, type, as
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/f63b0a857ccca52c.
Report an issue: GitHub.