{"record":{"id":"7654a3c39935e327","repo":"gastownhall/beads","slug":"storage-errvalidation","errorCode":"storage.ErrValidation","errorMessage":"%w: claim requires actor and issue ID","messagePattern":"%w: claim requires actor and issue ID","errorType":"validation","errorClass":"storage.ErrValidation","httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/public_claim.go","lineNumber":26,"sourceCode":"\n\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// ClaimCommitMessage names the claimed issue and its claimant in the storage\n// commit. It lives here so every implementation of the claim role spells it\n// identically and `bd dolt log` reads the same on all of them. The actor is in\n// it because that line IS the audit trail — which is why every surface\n// reaching the role validates the actor before calling it.\nfunc ClaimCommitMessage(issueID, actor string) string {\n\treturn fmt.Sprintf(\"bd: claim %s by %s\", issueID, actor)\n}\n\n// ExecuteClaim applies a guarded claim in tx and reports durable tables changed.\nfunc ExecuteClaim(ctx context.Context, tx *sql.Tx, request publicops.ClaimRequest) (publicops.ClaimResult, ChangedTables, error) {\n\tif request.Actor == \"\" || request.IssueID == \"\" {\n\t\treturn publicops.ClaimResult{}, nil, fmt.Errorf(\"%w: claim requires actor and issue ID\", storage.ErrValidation)\n\t}\n\t// ClaimIssueInTx routes a wisp id to the wisp tables and claims it there.\n\t// The role deliberately does not: the wisp plane is not claimable through\n\t// it, and the refusal lands here — before the pre-image read, and before\n\t// any write the enclosing transaction would have to roll back.\n\tif IsActiveWispInTx(ctx, tx, request.IssueID) {\n\t\treturn publicops.ClaimResult{}, nil, fmt.Errorf(\"%w: issue %s\", storage.ErrNotFound, request.IssueID)\n\t}\n\tclaimed, err := ClaimIssueInTx(ctx, tx, request.IssueID, request.Actor)\n\tif err != nil {\n\t\treturn publicops.ClaimResult{}, nil, classifyClaimRefusalInTx(ctx, tx, request.IssueID, err)\n\t}\n\t// The CAS matches no row when the actor already holds the issue in\n\t// progress, and ClaimIssueInTx reports that as success. The pre-image is\n\t// what tells the two apart, and staging nothing for the idempotent case is\n\t// what keeps a polling caller from minting empty version-control commits.\n\t// Judged under actorMatches, not verbatim (ga-v2k49): a caller re-claiming\n\t// its own in-progress issue under a different layer's spelling of its own","sourceCodeStart":8,"sourceCodeEnd":44,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/public_claim.go#L8-L44","documentation":"ExecuteClaim validates its ClaimRequest before doing any work: both Actor and IssueID must be non-empty, otherwise it returns storage.ErrValidation wrapped with this message. This is a pre-condition guard so an unattributed or untargeted claim never reaches the transaction.","triggerScenarios":"Calling ExecuteClaim with ClaimRequest{Actor: \"\"} or ClaimRequest{IssueID: \"\"}, e.g. an empty actor from an unset env/config value or an issue ID variable that was never populated.","commonSituations":"Missing BD_ACTOR-like config, a CLI flag not passed (--actor), an issue ID parsed from user input that came back empty after trimming.","solutions":["Populate both request.Actor and request.IssueID before calling ExecuteClaim","In CLI code, require the actor flag or derive it from config/user and fail fast with a usage message if empty","Trim and validate user-supplied IDs before constructing ClaimRequest"],"exampleFix":"// before\nres, err := ExecuteClaim(ctx, tx, publicops.ClaimRequest{IssueID: id})\n// after\nif actor == \"\" || id == \"\" {\n    return fmt.Errorf(\"claim needs --actor and an issue ID\")\n}\nres, err := ExecuteClaim(ctx, tx, publicops.ClaimRequest{Actor: actor, IssueID: id})","handlingStrategy":"validation","validationCode":"func claimRequestOK(r publicops.ClaimRequest) bool { return r.Actor != \"\" && r.IssueID != \"\" }","typeGuard":null,"tryCatchPattern":"if err != nil {\n  if errors.Is(err, storage.ErrValidation) {\n    // user-input problem: fix the request, do not retry\n    return fmt.Errorf(\"invalid claim request: %w\", err)\n  }\n  return err\n}","preventionTips":["Resolve the actor (config/env/flag) before constructing ClaimRequest","Fail fast in CLI arg parsing on empty actor or issue ID","Trim whitespace and reject empty results early"],"tags":["claim","validation","storage"],"backgroundTag":"missing-required-field","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}