gastownhall/beads · error

%w: claim next requires an actor

Error message

%w: claim next requires an actor

What it means

ValidateClaimNextRequest rejects a claim-next request with an empty Actor field, wrapping storage.ErrValidation. Every claim requires a named actor so assignee, lease, and audit events can be attributed; anonymous claims are a contract violation shared by all backends.

Source

Thrown at internal/storage/issueops/claim_next.go:26

	"github.com/steveyegge/beads/internal/storage"
	"github.com/steveyegge/beads/internal/types"
	publicops "github.com/steveyegge/beads/issueops"
)

// ClaimNextCommitMessage names the claim in the Dolt commit message, matching
// what the CLI's own per-command commit wrote before the claim moved onto the
// role. It is the only spelling: the id it names is the one the claim WON, so
// no caller could have composed it before the call.
func ClaimNextCommitMessage(issueID string) string {
	return "bd: claim ready " + issueID
}

// ValidateClaimNextRequest applies the request rules every ReadyClaimer
// implementation shares. It lives here rather than in each of them because a
// rule enforced on one backend and not the other is not a contract.
func ValidateClaimNextRequest(request publicops.ClaimNextRequest) error {
	if request.Actor == "" {
		return fmt.Errorf("%w: claim next requires an actor", storage.ErrValidation)
	}
	if request.Filter.Limit != nil {
		return fmt.Errorf("%w: claim next does not take a limit", storage.ErrValidation)
	}
	if request.Filter.Offset != 0 {
		return fmt.Errorf("%w: claim next does not take an offset", storage.ErrValidation)
	}
	// Brief is refused for the reason Limit and Offset are, and the reason is
	// sharper here: a claim does not read its row through the page's query at
	// all. ExecuteClaimNext refetches the winning row whole (GetIssueInTx) and
	// hydrates its counts itself, so the projection has nothing to apply to,
	// and a claim that accepted the field would answer a MUTATING request with
	// a fully-hydrated row carrying IsLitePartial=false — the caller's only
	// signal that it did not get what it asked for, saying it did.
	if request.Filter.Brief {
		return fmt.Errorf("%w: claim next does not take a projection", storage.ErrValidation)
	}
	return nil

View on GitHub (pinned to 71377f2769)

Solutions

  1. Pass --actor <name> on the command line
  2. Set the BEADS_ACTOR environment variable
  3. Ensure API callers populate ClaimNextRequest.Actor before validating

Example fix

// before
req := publicops.ClaimNextRequest{Filter: filter}
err := ValidateClaimNextRequest(req) // fails: requires an actor
// after
req := publicops.ClaimNextRequest{Filter: filter, Actor: os.Getenv("BEADS_ACTOR")}
if req.Actor == "" { req.Actor = username.Current() }
err := ValidateClaimNextRequest(req)
Defensive patterns

Strategy: validation

Validate before calling

func ensureActor(req publicops.ClaimNextRequest) (publicops.ClaimNextRequest, error) {
    if req.Actor == "" {
        a := os.Getenv("BEADS_ACTOR")
        if a == "" { u, err := user.Current(); if err != nil { return req, err }; a = u.Username }
        req.Actor = a
    }
    if err := types.CheckFieldLen("actor", req.Actor); err != nil { return req, err }
    return req, ValidateClaimNextRequest(req)
}

Try / catch

if err := ValidateClaimNextRequest(req); err != nil {
    if errors.Is(err, storage.ErrValidation) { return fmt.Errorf("fix request: %w", err) }
    return err
}

Prevention

When it happens

Trigger: Calling ClaimReadyIssueInTx/ExecuteClaimNext (or any ReadyClaimer) with ClaimNextRequest{Actor: ""} — e.g. BEADS_ACTOR unset and no --actor flag passed.

Common situations: Running `bd claim` in a CI/container where BEADS_ACTOR isn't set and whoami falls back to empty; scripts invoking the Go API without populating the request struct; ValidateCloseBatchRequest re-validating the same rules and hitting the shared validator.

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/d83db5f5c4916c1b. Report an issue: GitHub.