{"record":{"id":"549b4ac66db7ebab","repo":"gastownhall/beads","slug":"claim-actor-and-issue-id-must-not-be-empty","errorCode":null,"errorMessage":"claim: actor and issue ID must not be empty","messagePattern":"claim: actor and issue ID must not be empty","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/uow/issue_claimer.go","lineNumber":54,"sourceCode":"func NewIssueClaimer(provider UnitOfWorkProvider) (publicops.Claimer, error) {\n\tif isNilUnitOfWorkProvider(provider) {\n\t\treturn nil, fmt.Errorf(\"new issue claimer: unit-of-work provider must not be nil\")\n\t}\n\treturn &issueClaimer{provider: provider}, nil\n}\n\nvar _ publicops.Claimer = (*issueClaimer)(nil)\n\n// Claim runs the compare-and-set in a retried unit-of-work transaction.\n//\n// RETRY LIVES HERE, not in the caller. RunTxResult redoes the WHOLE attempt in\n// a FRESH unit of work when one loses Dolt's commit-time merge, because\n// re-committing a session the server already rolled back is a lost write. That\n// is the same place every other verb on this seam keeps it, and it is what\n// lets the role promise that a lost merge is retried rather than surfaced.\nfunc (c *issueClaimer) Claim(ctx context.Context, request publicops.ClaimRequest) (publicops.ClaimResult, error) {\n\tif request.Actor == \"\" || request.IssueID == \"\" {\n\t\treturn publicops.ClaimResult{}, validationError(fmt.Errorf(\"claim: actor and issue ID must not be empty\"))\n\t}\n\treturn RunTxResult(ctx, c.provider, func(ctx context.Context, uw UnitOfWork) (publicops.ClaimResult, string, error) {\n\t\tuc := uw.IssueUseCase()\n\t\tclaimed, err := uc.ClaimIssue(ctx, request.IssueID, request.Actor)\n\t\tif err != nil {\n\t\t\treturn publicops.ClaimResult{}, \"\", classifyClaimError(ctx, uc, request.IssueID, err)\n\t\t}\n\t\t// Read back INSIDE this transaction, so the result describes the row\n\t\t// this CAS wrote and not a later writer's.\n\t\tissue, err := uc.GetIssue(ctx, request.IssueID)\n\t\tif err != nil {\n\t\t\treturn publicops.ClaimResult{}, \"\", err\n\t\t}\n\t\tif issue == nil {\n\t\t\t// A miss with a nil error is the other shape a not-found takes at\n\t\t\t// this seam; normalize it rather than dereferencing nil.\n\t\t\treturn publicops.ClaimResult{}, \"\", fmt.Errorf(\"%w: issue %s\", publicops.ErrNotFound, request.IssueID)\n\t\t}","sourceCodeStart":36,"sourceCodeEnd":72,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/uow/issue_claimer.go#L36-L72","documentation":"Claim validates that the ClaimRequest carries both an actor identity and an issue ID before opening a transaction. The unit-of-work seam refuses to run ClaimIssue with empty identifiers because such a call can never match a row and would waste a transaction. It is thrown as a validationError, i.e. an input-shape problem, not a storage problem.","triggerScenarios":"Calling Claim(ctx, publicops.ClaimRequest{}) with Actor unset, IssueID unset, or both empty strings.","commonSituations":"Forgotten request wiring when building ClaimRequest programmatically; a variable holding the issue ID that was never populated; refactors that renamed Actor fields and left the assignment behind.","solutions":["Set both request.Actor and request.IssueID before calling Claim.","If the issue ID comes from user input, validate non-empty at the CLI/API boundary.","Verify the struct literal constructing ClaimRequest actually assigns both fields, not just the issue ID."],"exampleFix":"// before\nres, err := uow.Claim(ctx, publicops.ClaimRequest{IssueID: id})\n// after\nres, err := uow.Claim(ctx, publicops.ClaimRequest{Actor: actor, IssueID: id})","handlingStrategy":"validation","validationCode":"if req.Actor == \"\" || req.IssueID == \"\" {\n    return fmt.Errorf(\"claim requires actor and issue ID\")\n}","typeGuard":"func validClaimRequest(r publicops.ClaimRequest) bool {\n    return r.Actor != \"\" && r.IssueID != \"\"\n}","tryCatchPattern":null,"preventionTips":["Validate ClaimRequest fields at construction time.","Add a unit test covering zero-value ClaimRequest.","Keep actor identity mandatory in any request-builder helper."],"tags":["validation","input","claims"],"backgroundTag":"empty-required-field","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}