gastownhall/beads · error
invalid limit %d: a query limit must be zero or greater; 0 m
Error message
invalid limit %d: a query limit must be zero or greater; 0 means unlimited
What it means
BuildQueryPlan rejects a negative limit with this message, explaining that a query limit must be zero or greater and that 0 means unlimited. The limit is defaulted via LimitOr before the check, so this fires only when a caller explicitly supplies a negative number. The error carries issueops.ErrValidation so callers can classify it with errors.Is as a 400-style refusal.
Source
Thrown at internal/workapi/query.go:68
// evaluation, the default closed exclusion, the limit defaulting, and the row
// bound each shape of query may carry.
//
// THE ROW BOUND IS THE WHOLE REASON THIS IS ONE FUNCTION. A predicate query's
// Filter gets NO limit — the predicate must see every candidate row, or the
// page it produces is an arbitrary prefix of the answer reported as the whole
// of it (issueops/querier.go:118-133). The front doors used to bound that query
// at max(3*Limit, 100) and filter what came back; that bound is gone.
//
// It reads no configuration and touches no store, so both implementations share
// it without supplying a config source.
func BuildQueryPlan(in issueops.QueryRequest) (QueryPlan, error) {
expression := strings.TrimSpace(in.Expression)
if expression == "" {
return QueryPlan{}, invalidQueryExpression("an expression is required")
}
limit := LimitOr(in.Limit, DefaultQueryLimit)
if limit < 0 {
return QueryPlan{}, fmt.Errorf("invalid limit %d: a query limit must be zero or greater; 0 means unlimited%.0w",
limit, issueops.ErrValidation)
}
if in.Offset < 0 {
return QueryPlan{}, fmt.Errorf("invalid offset %d: a query offset must be zero or greater%.0w",
in.Offset, issueops.ErrValidation)
}
if in.Offset > 0 && in.SortBy != "" {
return QueryPlan{}, fmt.Errorf(
"invalid offset: an offset cannot be combined with a display order, because the order is applied to the rows the query bounded and each page would be sorted for itself%.0w",
issueops.ErrValidation)
}
node, err := query.Parse(expression)
if err != nil {
return QueryPlan{}, invalidQueryExpression(err.Error())
}
result, err := query.NewEvaluator(time.Now()).Evaluate(node)
if err != nil {View on GitHub (pinned to 71377f2769)
Solutions
- Pass 0 for unlimited or omit Limit to get DefaultQueryLimit instead of a negative value
- Clamp/validate page-size inputs at the boundary: if limit < 0 { limit = 0 }
- Fix the upstream arithmetic (e.g. remaining-page calculation) that produces the negative number
Example fix
// before
plan, err := BuildQueryPlan(issueops.QueryRequest{Expression: "status = open", Limit: -1})
// after
limit := pageSize
if limit < 0 { limit = 0 } // 0 means unlimited
plan, err := BuildQueryPlan(issueops.QueryRequest{Expression: "status = open", Limit: limit}) Defensive patterns
Strategy: validation
Validate before calling
if limit < 0 {
return errors.New("limit must be >= 0 (0 = unlimited)")
} Try / catch
plan, err := BuildQueryPlan(req)
if err != nil && errors.Is(err, issueops.ErrValidation) {
// client-input error; surface as 400, do not retry
} Prevention
- Treat 0 as unlimited and omit Limit for the default; never use -1 as a sentinel
- Clamp page-size inputs at the boundary
- Use typed option structs where Limit is *int so 'unset' is nil, not negative
When it happens
Trigger: Calling BuildQueryPlan with an issueops.QueryRequest whose Limit is negative, e.g. Limit=-1 from a CLI flag parse or an API client computing a page size.
Common situations: Subtraction-based pagination code going negative on the last page; a config where pageSize defaults to -1 meaning 'unset'; a client misreading 0 as 'unset' and using -1 instead.
Related errors
- invalid offset %d: a query offset must be zero or greater
- invalid offset: an offset cannot be combined with a display
- unknown field: %s
- invalid status: %s
- priority must be between 0 and 4
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/dced2329df9b873d.
Report an issue: GitHub.