{"record":{"id":"d83db5f5c4916c1b","repo":"gastownhall/beads","slug":"w-claim-next-requires-an-actor","errorCode":null,"errorMessage":"%w: claim next requires an actor","messagePattern":"%w: claim next requires an actor","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/claim_next.go","lineNumber":26,"sourceCode":"\t\"github.com/steveyegge/beads/internal/storage\"\n\t\"github.com/steveyegge/beads/internal/types\"\n\tpublicops \"github.com/steveyegge/beads/issueops\"\n)\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","sourceCodeStart":8,"sourceCodeEnd":44,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/claim_next.go#L8-L44","documentation":"ValidateClaimNextRequest rejects a claim-next request with an empty Actor field, wrapping storage.ErrValidation. Every claim requires a named actor so assignee, lease, and audit events can be attributed; anonymous claims are a contract violation shared by all backends.","triggerScenarios":"Calling ClaimReadyIssueInTx/ExecuteClaimNext (or any ReadyClaimer) with ClaimNextRequest{Actor: \"\"} — e.g. BEADS_ACTOR unset and no --actor flag passed.","commonSituations":"Running `bd claim` in a CI/container where BEADS_ACTOR isn't set and whoami falls back to empty; scripts invoking the Go API without populating the request struct; ValidateCloseBatchRequest re-validating the same rules and hitting the shared validator.","solutions":["Pass --actor <name> on the command line","Set the BEADS_ACTOR environment variable","Ensure API callers populate ClaimNextRequest.Actor before validating"],"exampleFix":"// before\nreq := publicops.ClaimNextRequest{Filter: filter}\nerr := ValidateClaimNextRequest(req) // fails: requires an actor\n// after\nreq := publicops.ClaimNextRequest{Filter: filter, Actor: os.Getenv(\"BEADS_ACTOR\")}\nif req.Actor == \"\" { req.Actor = username.Current() }\nerr := ValidateClaimNextRequest(req)","handlingStrategy":"validation","validationCode":"func ensureActor(req publicops.ClaimNextRequest) (publicops.ClaimNextRequest, error) {\n    if req.Actor == \"\" {\n        a := os.Getenv(\"BEADS_ACTOR\")\n        if a == \"\" { u, err := user.Current(); if err != nil { return req, err }; a = u.Username }\n        req.Actor = a\n    }\n    if err := types.CheckFieldLen(\"actor\", req.Actor); err != nil { return req, err }\n    return req, ValidateClaimNextRequest(req)\n}","typeGuard":null,"tryCatchPattern":"if err := ValidateClaimNextRequest(req); err != nil {\n    if errors.Is(err, storage.ErrValidation) { return fmt.Errorf(\"fix request: %w\", err) }\n    return err\n}","preventionTips":["Always set --actor or BEADS_ACTOR, especially in CI/containers","Populate ClaimNextRequest.Actor before calling validators","Validate requests once at the boundary with ValidateClaimNextRequest"],"tags":["validation","claim","configuration"],"backgroundTag":"missing-actor","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}