{"record":{"id":"fe6649b4243da2eb","repo":"gastownhall/beads","slug":"w-claim-next-does-not-take-a-limit","errorCode":null,"errorMessage":"%w: claim next does not take a limit","messagePattern":"%w: claim next does not take a limit","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/claim_next.go","lineNumber":29,"sourceCode":")\n\n// ClaimNextCommitMessage names the claim in the Dolt commit message, matching\n// what the CLI's own per-command commit wrote before the claim moved onto the\n// role. It is the only spelling: the id it names is the one the claim WON, so\n// no caller could have composed it before the call.\nfunc ClaimNextCommitMessage(issueID string) string {\n\treturn \"bd: claim ready \" + issueID\n}\n\n// ValidateClaimNextRequest applies the request rules every ReadyClaimer\n// implementation shares. It lives here rather than in each of them because a\n// rule enforced on one backend and not the other is not a contract.\nfunc ValidateClaimNextRequest(request publicops.ClaimNextRequest) error {\n\tif request.Actor == \"\" {\n\t\treturn fmt.Errorf(\"%w: claim next requires an actor\", storage.ErrValidation)\n\t}\n\tif request.Filter.Limit != nil {\n\t\treturn fmt.Errorf(\"%w: claim next does not take a limit\", storage.ErrValidation)\n\t}\n\tif request.Filter.Offset != 0 {\n\t\treturn fmt.Errorf(\"%w: claim next does not take an offset\", storage.ErrValidation)\n\t}\n\t// Brief is refused for the reason Limit and Offset are, and the reason is\n\t// sharper here: a claim does not read its row through the page's query at\n\t// all. ExecuteClaimNext refetches the winning row whole (GetIssueInTx) and\n\t// hydrates its counts itself, so the projection has nothing to apply to,\n\t// and a claim that accepted the field would answer a MUTATING request with\n\t// a fully-hydrated row carrying IsLitePartial=false — the caller's only\n\t// signal that it did not get what it asked for, saying it did.\n\tif request.Filter.Brief {\n\t\treturn fmt.Errorf(\"%w: claim next does not take a projection\", storage.ErrValidation)\n\t}\n\treturn nil\n}\n\n// ExecuteClaimNext claims the first ready issue matching filter in tx, hydrates","sourceCodeStart":11,"sourceCodeEnd":47,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/claim_next.go#L11-L47","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Clear Filter.Limit (set to nil) before passing the filter to claim next","Build a dedicated claim filter instead of reusing a list/ready pagination filter","Note that ClaimReadyIssueInTx already zeroes Limit/MaxRows internally — avoid pre-setting them"],"exampleFix":"// before\nfilter := types.WorkFilter{Status: &open, Limit: &limit}\nerr := ValidateClaimNextRequest(publicops.ClaimNextRequest{Filter: filter})\n// after\nfilter := types.WorkFilter{Status: &open}\nfilter.Limit = nil // claim delivers exactly one issue\nerr := ValidateClaimNextRequest(publicops.ClaimNextRequest{Filter: filter})","handlingStrategy":"validation","validationCode":"func sanitizeClaimFilter(f types.WorkFilter) types.WorkFilter {\n    f.Limit = nil\n    f.Offset = 0\n    f.Brief = false\n    return f\n}\n// then: ValidateClaimNextRequest(publicops.ClaimNextRequest{Filter: sanitizeClaimFilter(f), Actor: actor})","typeGuard":null,"tryCatchPattern":"if err := ValidateClaimNextRequest(req); err != nil {\n    if errors.Is(err, storage.ErrValidation) {\n        req.Filter.Limit = nil\n        return ValidateClaimNextRequest(req)\n    }\n    return err\n}","preventionTips":["Never reuse list/ready pagination filters for claim requests","Remember claim returns exactly one issue — no pagination knobs apply","Run ValidateClaimNextRequest before any ReadyClaimer call in shared code paths"],"tags":["validation","claim","pagination"],"backgroundTag":"invalid-claim-filter","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}