gastownhall/beads · error · storage.ErrValidation

: claim cannot use expected assignee or status

Error message

%w: claim cannot use expected assignee or status

What it means

This error is returned by ValidateUpdateRequest in internal/storage/issueops/aggregate.go when an update request sets Claim=true while also supplying ExpectedAssignee or ExpectedStatus. Claim is a guarded fast-path that asserts the issue is claimable, so combining it with optimistic-concurrency expectation fields is ambiguous and rejected before any row is touched. It wraps storage.ErrValidation so callers can classify it with errors.Is.

Solutions

  1. Remove ExpectedAssignee and ExpectedStatus from the request when Claim is true — the claim guard does its own concurrency checking
  2. Split the operation: if you need expectation-based checks, drop Claim and use a plain update with ExpectedAssignee/ExpectedStatus
  3. Inspect the request-assembly code path and only set expectation fields on non-claim updates

Example fix

// before
req := publicops.UpdateRequest{ID: id, Claim: true, ExpectedStatus: &st}
_, err := issueops.ExecuteUpdate(ctx, tx, req)
// after
req := publicops.UpdateRequest{ID: id, Claim: true}
_, err := issueops.ExecuteUpdate(ctx, tx, req)
Defensive patterns

Strategy: validation

Validate before calling

func validClaimRequest(r publicops.UpdateRequest) bool {
  return r.Claim && r.ExpectedAssignee == nil && r.ExpectedStatus == nil
}
if !validClaimRequest(req) { return errors.New("claim cannot carry expected assignee/status") }

Type guard

func isPureClaim(r publicops.UpdateRequest) bool {
  return r.Claim && r.ExpectedAssignee == nil && r.ExpectedStatus == nil
}

Try / catch

err := issueops.ValidateUpdateRequest(req)
if errors.Is(err, storage.ErrValidation) {
  // reject/repair request before ExecuteUpdate
}

Prevention

When it happens

Trigger: Calling ExecuteUpdate (or building a publicops.UpdateRequest) with request.Claim=true and a non-nil ExpectedAssignee pointer, or with Claim=true and a non-nil ExpectedStatus pointer. Either combination triggers it immediately.

Common situations: Code that programmatically builds update requests and reuses a struct with leftover expectation fields; migrating from an explicit expected-assignee claim flow to the new Claim shortcut while still populating the old fields; UI or CLI layers that always set expected status for CAS updates then add claim.

Related errors


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

Appendix: source

Thrown at internal/storage/issueops/aggregate.go:74

		{patch.ClosedBySession.Set, "closed_by_session", patch.ClosedBySession.Value},
		{patch.EstimatedMinutes.Set, "estimated_minutes", patch.EstimatedMinutes.Value},
		{patch.ExternalRef.Set, "external_ref", patch.ExternalRef.Value},
		{patch.DueAt.Set, "due_at", patch.DueAt.Value},
		{patch.DeferUntil.Set, "defer_until", patch.DeferUntil.Value},
	} {
		if field.set {
			updates[field.key] = field.val
		}
	}
	return updates
}

// ValidateUpdateRequest checks mutually exclusive guarded-update options and
// the canonical field values every backend must reject identically. Backends
// call it before touching the row so an invalid patch cannot half-apply.
func ValidateUpdateRequest(request publicops.UpdateRequest) error {
	if request.Claim && (request.ExpectedAssignee != nil || request.ExpectedStatus != nil) {
		return fmt.Errorf("%w: claim cannot use expected assignee or status", storage.ErrValidation)
	}
	if request.ForceAssigneeTransfer && (request.Claim || !request.Patch.Assignee.Set || request.ExpectedAssignee != nil) {
		return fmt.Errorf("%w: invalid forced assignee transfer", storage.ErrValidation)
	}
	patch := request.Patch
	if patch.Title.Set {
		if err := types.ValidateIssueTitle(patch.Title.Value); err != nil {
			return fmt.Errorf("%w: update title: %w", storage.ErrValidation, err)
		}
	}
	if patch.Priority.Set {
		if err := types.ValidateIssuePriority(patch.Priority.Value); err != nil {
			return fmt.Errorf("%w: update priority: %w", storage.ErrValidation, err)
		}
	}
	if patch.EstimatedMinutes.Set {
		if err := types.ValidateIssueEstimatedMinutes(patch.EstimatedMinutes.Value); err != nil {
			return fmt.Errorf("%w: update estimated_minutes: %w", storage.ErrValidation, err)

View on GitHub (pinned to 71377f2769)