gastownhall/beads · error
claim: actor and issue ID must not be empty
Error message
claim: actor and issue ID must not be empty
What it means
Claim validates that the ClaimRequest carries both an actor identity and an issue ID before opening a transaction. The unit-of-work seam refuses to run ClaimIssue with empty identifiers because such a call can never match a row and would waste a transaction. It is thrown as a validationError, i.e. an input-shape problem, not a storage problem.
Source
Thrown at internal/storage/uow/issue_claimer.go:54
func NewIssueClaimer(provider UnitOfWorkProvider) (publicops.Claimer, error) {
if isNilUnitOfWorkProvider(provider) {
return nil, fmt.Errorf("new issue claimer: unit-of-work provider must not be nil")
}
return &issueClaimer{provider: provider}, nil
}
var _ publicops.Claimer = (*issueClaimer)(nil)
// Claim runs the compare-and-set in a retried unit-of-work transaction.
//
// RETRY LIVES HERE, not in the caller. RunTxResult redoes the WHOLE attempt in
// a FRESH unit of work when one loses Dolt's commit-time merge, because
// re-committing a session the server already rolled back is a lost write. That
// is the same place every other verb on this seam keeps it, and it is what
// lets the role promise that a lost merge is retried rather than surfaced.
func (c *issueClaimer) Claim(ctx context.Context, request publicops.ClaimRequest) (publicops.ClaimResult, error) {
if request.Actor == "" || request.IssueID == "" {
return publicops.ClaimResult{}, validationError(fmt.Errorf("claim: actor and issue ID must not be empty"))
}
return RunTxResult(ctx, c.provider, func(ctx context.Context, uw UnitOfWork) (publicops.ClaimResult, string, error) {
uc := uw.IssueUseCase()
claimed, err := uc.ClaimIssue(ctx, request.IssueID, request.Actor)
if err != nil {
return publicops.ClaimResult{}, "", classifyClaimError(ctx, uc, request.IssueID, err)
}
// Read back INSIDE this transaction, so the result describes the row
// this CAS wrote and not a later writer's.
issue, err := uc.GetIssue(ctx, request.IssueID)
if err != nil {
return publicops.ClaimResult{}, "", err
}
if issue == nil {
// A miss with a nil error is the other shape a not-found takes at
// this seam; normalize it rather than dereferencing nil.
return publicops.ClaimResult{}, "", fmt.Errorf("%w: issue %s", publicops.ErrNotFound, request.IssueID)
}View on GitHub (pinned to 71377f2769)
Solutions
- Set both request.Actor and request.IssueID before calling Claim.
- If the issue ID comes from user input, validate non-empty at the CLI/API boundary.
- Verify the struct literal constructing ClaimRequest actually assigns both fields, not just the issue ID.
Example fix
// before
res, err := uow.Claim(ctx, publicops.ClaimRequest{IssueID: id})
// after
res, err := uow.Claim(ctx, publicops.ClaimRequest{Actor: actor, IssueID: id}) Defensive patterns
Strategy: validation
Validate before calling
if req.Actor == "" || req.IssueID == "" {
return fmt.Errorf("claim requires actor and issue ID")
} Type guard
func validClaimRequest(r publicops.ClaimRequest) bool {
return r.Actor != "" && r.IssueID != ""
} Prevention
- Validate ClaimRequest fields at construction time.
- Add a unit test covering zero-value ClaimRequest.
- Keep actor identity mandatory in any request-builder helper.
When it happens
Trigger: Calling Claim(ctx, publicops.ClaimRequest{}) with Actor unset, IssueID unset, or both empty strings.
Common situations: Forgotten request wiring when building ClaimRequest programmatically; a variable holding the issue ID that was never populated; refactors that renamed Actor fields and left the assignment behind.
Related errors
- ErrFieldTooLong
- title is required
- close: id must not be empty
- close: actor must not be empty
- add labels: id must not be empty
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/549b4ac66db7ebab.
Report an issue: GitHub.