gastownhall/beads · error

%w: claim next does not take a limit

Error message

%w: claim next does not take a limit

What it means

ValidateClaimNextRequest rejects claim-next requests that set a Limit on the work filter, wrapping storage.ErrValidation. Claim delivers exactly one issue, so a limit is meaningless here; the shared validator enforces this uniformly across backends so the contract does not drift.

Source

Thrown at internal/storage/issueops/claim_next.go:29

)

// ClaimNextCommitMessage names the claim in the Dolt commit message, matching
// what the CLI's own per-command commit wrote before the claim moved onto the
// role. It is the only spelling: the id it names is the one the claim WON, so
// no caller could have composed it before the call.
func ClaimNextCommitMessage(issueID string) string {
	return "bd: claim ready " + issueID
}

// ValidateClaimNextRequest applies the request rules every ReadyClaimer
// implementation shares. It lives here rather than in each of them because a
// rule enforced on one backend and not the other is not a contract.
func ValidateClaimNextRequest(request publicops.ClaimNextRequest) error {
	if request.Actor == "" {
		return fmt.Errorf("%w: claim next requires an actor", storage.ErrValidation)
	}
	if request.Filter.Limit != nil {
		return fmt.Errorf("%w: claim next does not take a limit", storage.ErrValidation)
	}
	if request.Filter.Offset != 0 {
		return fmt.Errorf("%w: claim next does not take an offset", storage.ErrValidation)
	}
	// Brief is refused for the reason Limit and Offset are, and the reason is
	// sharper here: a claim does not read its row through the page's query at
	// all. ExecuteClaimNext refetches the winning row whole (GetIssueInTx) and
	// hydrates its counts itself, so the projection has nothing to apply to,
	// and a claim that accepted the field would answer a MUTATING request with
	// a fully-hydrated row carrying IsLitePartial=false — the caller's only
	// signal that it did not get what it asked for, saying it did.
	if request.Filter.Brief {
		return fmt.Errorf("%w: claim next does not take a projection", storage.ErrValidation)
	}
	return nil
}

// ExecuteClaimNext claims the first ready issue matching filter in tx, hydrates

View on GitHub (pinned to 71377f2769)

Solutions

  1. Clear Filter.Limit (set to nil) before passing the filter to claim next
  2. Build a dedicated claim filter instead of reusing a list/ready pagination filter
  3. Note that ClaimReadyIssueInTx already zeroes Limit/MaxRows internally — avoid pre-setting them

Example fix

// before
filter := types.WorkFilter{Status: &open, Limit: &limit}
err := ValidateClaimNextRequest(publicops.ClaimNextRequest{Filter: filter})
// after
filter := types.WorkFilter{Status: &open}
filter.Limit = nil // claim delivers exactly one issue
err := ValidateClaimNextRequest(publicops.ClaimNextRequest{Filter: filter})
Defensive patterns

Strategy: validation

Validate before calling

func sanitizeClaimFilter(f types.WorkFilter) types.WorkFilter {
    f.Limit = nil
    f.Offset = 0
    f.Brief = false
    return f
}
// then: ValidateClaimNextRequest(publicops.ClaimNextRequest{Filter: sanitizeClaimFilter(f), Actor: actor})

Try / catch

if err := ValidateClaimNextRequest(req); err != nil {
    if errors.Is(err, storage.ErrValidation) {
        req.Filter.Limit = nil
        return ValidateClaimNextRequest(req)
    }
    return err
}

Prevention

When it happens

Trigger: Calling a ReadyClaimer with ClaimNextRequest whose Filter.Limit is non-nil — e.g. reusing a list/ready page filter (with pagination set) as the claim filter.

Common situations: Scripts that build one types.WorkFilter for both `bd list` (paged) and claim; copying a ready-query filter into the claim request without clearing Limit/Offset/Brief; API consumers applying a BEADS_MAX_ROWS cap to the claim filter.

Related errors


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