gastownhall/beads · error
%w: claim next does not take a projection
Error message
%w: claim next does not take a projection
What it means
ValidateClaimNextRequest rejects ClaimNextRequest.Filter.Brief=true. A claim does not read its row through the paged query at all: ExecuteClaimNext refetches the winning row whole and hydrates counts itself, so a 'brief' projection has nothing to apply to. Accepting it would return a fully-hydrated row with IsLitePartial=false, falsely signaling the caller got the projection it asked for.
Source
Thrown at internal/storage/issueops/claim_next.go:42
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
// it in that same transaction, and reports the durable tables changed.
//
// It takes the filter rather than the public request because this package
// speaks WorkFilter: the request-to-filter translation is internal/workapi's,
// and the same builder that answers Reader.Ready is what the accessor
// implementations run before calling in here. That is what keeps the claim's
// predicate and the listing's predicate the same predicate.
//
// A nil claim with a nil error is the ordinary empty-front outcome. It reports
// no changed tables, so the caller's transaction commits nothing and no
// history entry is recorded.
func ExecuteClaimNext(ctx context.Context, tx *sql.Tx, actor string, filter types.WorkFilter) (publicops.ClaimNextResult, ChangedTables, error) {
claimed, err := ClaimReadyIssueInTx(ctx, tx, filter, actor)View on GitHub (pinned to 71377f2769)
Solutions
- Set request.Filter.Brief = false before calling ClaimNext
- Use a dedicated WorkFilter for claims, separate from listing filters
- If the caller wants a light row, strip fields from the returned ClaimNextResult client-side instead of asking the storage layer to project
Example fix
// before req.Filter = readyListFilter // Brief: true result, err := store.ClaimNext(ctx, req) // after req.Filter = readyListFilter req.Filter.Brief = false // claims always return fully hydrated rows result, err := store.ClaimNext(ctx, req)
Defensive patterns
Strategy: validation
Validate before calling
if req.Filter.Brief {
return errors.New("ClaimNext always returns a fully hydrated row; Brief is not supported")
} Type guard
func noProjection(f types.WorkFilter) bool { return !f.Brief } Prevention
- Keep claim filters separate from listing/projection filters
- Strip fields from the returned result instead of requesting a projection
- Reset Brief (and Limit/Offset) whenever converting a ready-list filter into a claim request
When it happens
Trigger: Calling ClaimNext with request.Filter.Brief set to true, usually by reusing a filter from a brief/lite ready listing.
Common situations: Code paths that request brief rows to save bandwidth for listings then reuse the same filter struct for claiming; CLI flags like --brief or a lite-mode toggle left enabled when claiming work.
Related errors
- %w: claim next does not take an offset
- no store is open for this workspace
- not found
- no absolute native user directory is available
- ExternalDoltConfig: set either Socket OR (Host, Port), not b
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/c63fda6da42efd43.
Report an issue: GitHub.