gastownhall/beads · error
invalid offset %d: a query offset must be zero or greater
Error message
invalid offset %d: a query offset must be zero or greater
What it means
BuildQueryPlan rejects a negative offset with this message; offsets must be zero or greater. Like the limit check, it runs before query parsing and wraps issueops.ErrValidation for errors.Is classification. A negative offset has no meaning in row pagination, so it is refused rather than clamped.
Source
Thrown at internal/workapi/query.go:72
// 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 {
return QueryPlan{}, invalidQueryExpression(err.Error())
}
plan := QueryPlan{View on GitHub (pinned to 71377f2769)
Solutions
- Compute offset as max(0, (page-1)*pageSize) before building the request
- Use 0 for the first page and omit Offset when not paginating
- Treat 'unset' as 0 or a nil pointer, not -1
Example fix
// before
offset := (page - 1) * pageSize // page=0 → offset=-20
// after
if page < 1 { page = 1 }
offset := (page - 1) * pageSize Defensive patterns
Strategy: validation
Validate before calling
if offset < 0 {
return errors.New("offset must be >= 0")
} Try / catch
plan, err := BuildQueryPlan(req)
if err != nil && errors.Is(err, issueops.ErrValidation) {
// refuse the request; do not retry
} Prevention
- Compute offset as max(0, (page-1)*pageSize)
- Model 'unset' with nil/0, never -1
- Unit-test pagination math for page 0 and negative page inputs
When it happens
Trigger: Calling BuildQueryPlan with issueops.QueryRequest.Offset < 0, typically from computed pagination (page-1)*pageSize when page was 0 or negative, or a client sending offset=-20.
Common situations: Page-number arithmetic bugs in API clients; an 'unset' sentinel of -1 leaking into the request; deserializing a JSON body where offset was omitted and a default of -1 applied.
Related errors
- invalid limit %d: a query limit must be zero or greater; 0 m
- 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/d480a9bf790ed361.
Report an issue: GitHub.