{"record":{"id":"514f1ce0213a2a32","repo":"gastownhall/beads","slug":"release-requires-an-issue-id","errorCode":null,"errorMessage":"release requires an issue id","messagePattern":"release requires an issue id","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/workapi/release.go","lineNumber":29,"sourceCode":"// 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\",\n\t\t\t\tissueops.ErrValidation)\n\t\t}\n\t}","sourceCodeStart":11,"sourceCodeEnd":47,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/workapi/release.go#L11-L47","documentation":"ValidateReleaseRequest refuses a ReleaseRequest whose IssueID is empty (or whitespace-only). A release names the issue whose assignee is being released; with no issue id the request is meaningless, so it is rejected up front before any database work. All Releaser implementations share this rule.","triggerScenarios":"Calling a Releaser implementation with issueops.ReleaseRequest{IssueID: \"\"} (or whitespace) even when Actor and ExpectedAssignee are correctly set.","commonSituations":"A CLI handler built the request before parsing the positional issue argument; a loop over issues passed an empty string from an unset variable; a caller constructed the struct with named fields and forgot IssueID.","solutions":["Set ReleaseRequest.IssueID to the id of the issue to release (e.g. \"bd-42\")","Verify the issue id was actually parsed/populated from user input before building the request","Handle errors.Is(err, issueops.ErrValidation) with an 'issue id required' message"],"exampleFix":"// before\nreq := issueops.ReleaseRequest{Actor: \"alice\"}\n// after\nreq := issueops.ReleaseRequest{Actor: \"alice\", IssueID: issueID}","handlingStrategy":"validation","validationCode":"if strings.TrimSpace(req.IssueID) == \"\" {\n    return fmt.Errorf(\"cannot release: issue id is required\")\n}","typeGuard":"func hasIssueID(req issueops.ReleaseRequest) bool {\n    return strings.TrimSpace(req.IssueID) != \"\"\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":["Parse the issue id argument before constructing the request and fail with a usage message if absent","Never build ReleaseRequest literals with partially-known fields; use a constructor that takes issueID and actor","Validate the id format (e.g. prefix check) alongside emptiness"],"tags":["go","validation","release","missing-issue-id"],"backgroundTag":"missing-required-field","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}