gastownhall/beads · error
a ready count does not take an offset
Error message
a ready count does not take an offset
What it means
BuildReadyCountFilter likewise refuses a non-zero Offset: skipping rows would require subtracting them from a set that still holds them, which a count cannot express. It wraps issueops.ErrValidation so callers classify via errors.Is.
Source
Thrown at internal/workapi/ready.go:121
}
// 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.
return BuildReadyFilter(counted)
}
View on GitHub (pinned to 71377f2769)
Solutions
- Set Offset to 0 on the request passed to BuildReadyCountFilter
- Construct the count request separately from the paged listing request
- If you need 'remaining count after skip', fetch the unbounded count and subtract the offset client-side
Example fix
// before req.Offset = 20 countFilter, err := BuildReadyCountFilter(req) // after req.Offset = 0 countFilter, err := BuildReadyCountFilter(req) // subtract 20 from the result if needed
Defensive patterns
Strategy: validation
Validate before calling
if req.Offset != 0 {
return errors.New("count requests must not set an offset")
} Try / catch
filter, err := BuildReadyCountFilter(req)
if err != nil && errors.Is(err, issueops.ErrValidation) && strings.Contains(err.Error(), "does not take an offset") {
// zero Offset and rebuild; subtract manually if needed
} Prevention
- Zero the Offset when converting a paged request into a count request
- Compute 'remaining' by subtracting the offset from the unbounded count client-side
- Do not let shared pagination middleware mutate count requests
When it happens
Trigger: Calling BuildReadyCountFilter with in.Offset != 0, e.g. forwarding the offset of the current page from a paged ready listing into the count request.
Common situations: Handler code copying the list request (offset included) into the count call; clients computing total-after-skip counts; pagination middleware injecting Offset into every request uniformly.
Related errors
- a ready count does not take a limit
- 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/9f3b64d9038de751.
Report an issue: GitHub.