{"record":{"id":"906b206755becdda","repo":"gastownhall/beads","slug":"release-requires-an-actor-to-attribute-it-to","errorCode":null,"errorMessage":"release requires an actor to attribute it to","messagePattern":"release requires an actor to attribute it to","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/workapi/release.go","lineNumber":26,"sourceCode":")\n\n// The shared, DATABASE-FREE half of issueops.Releaser: what a release request\n// means before anything is read.\n//\n// Every implementation runs it, so `bd unclaim` has one definition of a\n// malformed request rather than one per backend, and a refused request costs no\n// database work anywhere.\n//\n// What is NOT here is the release. Classifying the refusals needs the row, and\n// the row and the release must see one snapshot\n// (issueops.Releaser.Release); that body is\n// internal/storage/issueops.ReleaseIssueInTx, which all three legs reach.\n\n// ValidateReleaseRequest applies the request rules every Releaser\n// implementation shares.\nfunc ValidateReleaseRequest(in issueops.ReleaseRequest) error {\n\tif strings.TrimSpace(in.Actor) == \"\" {\n\t\treturn fmt.Errorf(\"%w: release requires an actor to attribute it to\", issueops.ErrValidation)\n\t}\n\tif strings.TrimSpace(in.IssueID) == \"\" {\n\t\treturn fmt.Errorf(\"%w: release requires an issue id\", issueops.ErrValidation)\n\t}\n\tif in.ExpectedAssignee != nil {\n\t\t// A non-nil pointer to \"\" is NOT \"expected unassigned\" here, unlike\n\t\t// UpdateRequest.ExpectedAssignee: releasing a row nobody holds is not a\n\t\t// release, and the raw seam beneath this role refuses the empty\n\t\t// expectation in as many words.\n\t\tif strings.TrimSpace(*in.ExpectedAssignee) == \"\" {\n\t\t\treturn fmt.Errorf(\"%w: expected assignee must name a holder; there is no release of an unheld issue\",\n\t\t\t\tissueops.ErrValidation)\n\t\t}\n\t\t// The two are answers to the same question and they disagree, which is\n\t\t// the rule UpdateRequest states for ForceAssigneeTransfer beside its\n\t\t// own ExpectedAssignee.\n\t\tif in.Force {\n\t\t\treturn fmt.Errorf(\"%w: force releases whoever holds the issue and expected-assignee releases only a named holder; a request cannot ask for both\",","sourceCodeStart":8,"sourceCodeEnd":44,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/workapi/release.go#L8-L44","documentation":"ValidateReleaseRequest refuses a ReleaseRequest whose Actor field is empty (or whitespace-only). Every release must be attributed to an actor, so the shared request validator rejects the request before any database work. All Releaser implementations run this check, giving `bd unclaim` one definition of a malformed request across backends.","triggerScenarios":"Calling any Releaser implementation (which calls ValidateReleaseRequest) with issueops.ReleaseRequest{Actor: \"\"} or Actor set to whitespace only, while IssueID and other fields are valid.","commonSituations":"A CLI handler forgot to plumb the --actor flag or the authenticated user into the request; a programmatic caller built the struct literally and left Actor as the zero value; an env/config lookup for the actor name returned an empty string.","solutions":["Set ReleaseRequest.Actor to a non-empty, non-whitespace identifier for who is releasing","Check where the actor value is sourced (flag, auth context, env) — fix the empty source","Compare against errors.Is(err, issueops.ErrValidation) and surface a user-facing 'actor required' message"],"exampleFix":"// before\nreq := issueops.ReleaseRequest{IssueID: \"bd-42\"}\nerr := releaser.Release(ctx, req)\n// after\nif strings.TrimSpace(actor) == \"\" {\n    return fmt.Errorf(\"--actor is required\")\n}\nreq := issueops.ReleaseRequest{IssueID: \"bd-42\", Actor: actor}\nerr := releaser.Release(ctx, req)","handlingStrategy":"validation","validationCode":"if strings.TrimSpace(req.Actor) == \"\" {\n    return fmt.Errorf(\"cannot release: actor is required\")\n}","typeGuard":"func hasActor(req issueops.ReleaseRequest) bool {\n    return strings.TrimSpace(req.Actor) != \"\"\n}","tryCatchPattern":"if err := releaser.Release(ctx, req); err != nil {\n    if errors.Is(err, issueops.ErrValidation) {\n        return fmt.Errorf(\"invalid release request: %w\", err)\n    }\n    return err\n}","preventionTips":["Populate Actor from a single resolved identity source (flag, auth, config) and assert it before building requests","Add a struct-constructor helper that requires the actor as a parameter","Test request-building code paths with an unset actor to catch regressions"],"tags":["go","validation","release","missing-actor"],"backgroundTag":"missing-required-field","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}