{"record":{"id":"86a79c36769597a1","repo":"gastownhall/beads","slug":"force-releases-whoever-holds-the-issue-and-expecte","errorCode":null,"errorMessage":"force releases whoever holds the issue and expected-assignee releases only a named holder; a request cannot ask for both","messagePattern":"force releases whoever holds the issue and expected-assignee releases only a named holder; a request cannot ask for both","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/workapi/release.go","lineNumber":44,"sourceCode":"\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}\n\treturn nil\n}\n","sourceCodeStart":26,"sourceCodeEnd":50,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/workapi/release.go#L26-L50","documentation":"ValidateReleaseRequest refuses a request that sets both ExpectedAssignee (non-nil, non-empty) and Force=true. Force releases whoever currently holds the issue; ExpectedAssignee releases only a named holder. They answer the same question and disagree, so a single request cannot ask for both — mirroring UpdateRequest's rule for ForceAssigneeTransfer beside its own ExpectedAssignee.","triggerScenarios":"Calling a Releaser with issueops.ReleaseRequest{Actor: set, IssueID: set, ExpectedAssignee: ptr(\"bob\"), Force: true} — both the conditional-guard and the unconditional-force semantics requested at once.","commonSituations":"A CLI tool exposed both --force and --expected-assignee and passed both through without mutual exclusion; a caller merged two request-building code paths, one setting Force and one setting ExpectedAssignee.","solutions":["Set Force=false and keep ExpectedAssignee to release only if the named holder still holds it","Set ExpectedAssignee=nil and keep Force=true to release whoever holds it","Add mutual-exclusion validation at the CLI/config layer so the two options cannot be combined"],"exampleFix":"// before\nreq := issueops.ReleaseRequest{Actor: \"alice\", IssueID: \"bd-1\", ExpectedAssignee: &holder, Force: true}\n// after\nreq := issueops.ReleaseRequest{Actor: \"alice\", IssueID: \"bd-1\", ExpectedAssignee: &holder} // drop Force, or drop ExpectedAssignee","handlingStrategy":"validation","validationCode":"if req.Force && req.ExpectedAssignee != nil {\n    return fmt.Errorf(\"cannot combine Force with ExpectedAssignee\")\n}","typeGuard":"func releaseModeIsValid(req issueops.ReleaseRequest) bool {\n    return !(req.Force && req.ExpectedAssignee != nil)\n}","tryCatchPattern":"if err := releaser.Release(ctx, req); err != nil {\n    if errors.Is(err, issueops.ErrValidation) {\n        return fmt.Errorf(\"conflicting release options: %w\", err)\n    }\n    return err\n}","preventionTips":["Expose Force and ExpectedAssignee as mutually exclusive CLI flags (flag level mutual exclusion)","Decide the release mode first (conditional vs forced) and build the request from one branch only","Add a lint/test asserting the two are never set together in request-construction code"],"tags":["go","validation","release","conflicting-options"],"backgroundTag":"conflicting-parameters","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}