gastownhall/beads · error
release requires an actor to attribute it to
Error message
release requires an actor to attribute it to
What it means
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.
Source
Thrown at internal/workapi/release.go:26
)
// The shared, DATABASE-FREE half of issueops.Releaser: what a release request
// means before anything is read.
//
// Every implementation runs it, so `bd unclaim` has one definition of a
// malformed request rather than one per backend, and a refused request costs no
// database work anywhere.
//
// What is NOT here is the release. Classifying the refusals needs the row, and
// the row and the release must see one snapshot
// (issueops.Releaser.Release); that body is
// internal/storage/issueops.ReleaseIssueInTx, which all three legs reach.
// ValidateReleaseRequest applies the request rules every Releaser
// implementation shares.
func ValidateReleaseRequest(in issueops.ReleaseRequest) error {
if strings.TrimSpace(in.Actor) == "" {
return fmt.Errorf("%w: release requires an actor to attribute it to", issueops.ErrValidation)
}
if strings.TrimSpace(in.IssueID) == "" {
return fmt.Errorf("%w: release requires an issue id", issueops.ErrValidation)
}
if in.ExpectedAssignee != nil {
// A non-nil pointer to "" is NOT "expected unassigned" here, unlike
// UpdateRequest.ExpectedAssignee: releasing a row nobody holds is not a
// release, and the raw seam beneath this role refuses the empty
// expectation in as many words.
if strings.TrimSpace(*in.ExpectedAssignee) == "" {
return fmt.Errorf("%w: expected assignee must name a holder; there is no release of an unheld issue",
issueops.ErrValidation)
}
// The two are answers to the same question and they disagree, which is
// the rule UpdateRequest states for ForceAssigneeTransfer beside its
// own ExpectedAssignee.
if in.Force {
return fmt.Errorf("%w: force releases whoever holds the issue and expected-assignee releases only a named holder; a request cannot ask for both",View on GitHub (pinned to 71377f2769)
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
Example fix
// before
req := issueops.ReleaseRequest{IssueID: "bd-42"}
err := releaser.Release(ctx, req)
// after
if strings.TrimSpace(actor) == "" {
return fmt.Errorf("--actor is required")
}
req := issueops.ReleaseRequest{IssueID: "bd-42", Actor: actor}
err := releaser.Release(ctx, req) Defensive patterns
Strategy: validation
Validate before calling
if strings.TrimSpace(req.Actor) == "" {
return fmt.Errorf("cannot release: actor is required")
} Type guard
func hasActor(req issueops.ReleaseRequest) bool {
return strings.TrimSpace(req.Actor) != ""
} Try / catch
if err := releaser.Release(ctx, req); err != nil {
if errors.Is(err, issueops.ErrValidation) {
return fmt.Errorf("invalid release request: %w", err)
}
return err
} Prevention
- 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
When it happens
Trigger: Calling any Releaser implementation (which calls ValidateReleaseRequest) with issueops.ReleaseRequest{Actor: ""} or Actor set to whitespace only, while IssueID and other fields are valid.
Common situations: 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.
Understand the failure class
Background: "Missing required field" and "field is required" errors: why libraries reject payloads that omit mandatory fields — this error's family across 20 libraries.
Related errors
- release requires an issue id
- expected assignee must name a holder; there is no release of
- force releases whoever holds the issue and expected-assignee
- no store is open for this workspace
- not found
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/906b206755becdda.
Report an issue: GitHub.