gastownhall/beads · error · issueops.ErrValidation

%w: compare-and-set requires an actor to attribute the swap

Error message

%w: compare-and-set requires an actor to attribute the swap to

What it means

PlanCompareAndSetKey validates its CompareAndSetKeyRequest and fails fast with the sentinel issueops.ErrValidation when in.Actor is empty. A compare-and-set swap must be attributed to an actor for audit purposes, so the library refuses to plan the operation without one. Callers should match on errors.Is(err, issueops.ErrValidation) to distinguish it from I/O failures.

Source

Thrown at internal/storage/metadata_cas.go:60

	// Expected is the canonical encoding of the value the key must hold, or nil
	// when the request requires the key to be ABSENT.
	Expected *json.RawMessage
	// Value is the canonical encoding of the value to store, or nil when the
	// request removes the key.
	Value *json.RawMessage
}

// PlanCompareAndSetKey validates a compare-and-set request and canonicalizes
// its values. It is the whole of the role's request validation: every
// implementation calls it before touching a substrate, so a refused request
// costs no database work anywhere.
//
// It COPIES both raw values rather than aliasing the caller's, because the
// canonical form is written into the plan and the request belongs to the caller
// for the whole call.
func PlanCompareAndSetKey(in issueops.CompareAndSetKeyRequest) (CompareAndSetKeyPlan, error) {
	if in.Actor == "" {
		return CompareAndSetKeyPlan{}, fmt.Errorf(
			"%w: compare-and-set requires an actor to attribute the swap to", issueops.ErrValidation)
	}
	if in.IssueID == "" {
		return CompareAndSetKeyPlan{}, fmt.Errorf(
			"%w: compare-and-set requires an issue id", issueops.ErrValidation)
	}
	if err := ValidateMetadataKey(in.Key); err != nil {
		return CompareAndSetKeyPlan{}, fmt.Errorf("%w: %v", issueops.ErrValidation, err)
	}
	plan := CompareAndSetKeyPlan{Actor: in.Actor, IssueID: in.IssueID, Key: in.Key}
	var err error
	if plan.Expected, err = CanonicalMetadataPointer(in.Expected); err != nil {
		return CompareAndSetKeyPlan{}, fmt.Errorf("%w: expected value for metadata key %q: %v",
			issueops.ErrValidation, in.Key, err)
	}
	if plan.Value, err = CanonicalMetadataPointer(in.Value); err != nil {
		return CompareAndSetKeyPlan{}, fmt.Errorf("%w: new value for metadata key %q: %v",
			issueops.ErrValidation, in.Key, err)

View on GitHub (pinned to 71377f2769)

Solutions

  1. Populate in.Actor with the issuing identity (username, agent id, or email) before calling PlanCompareAndSetKey.
  2. On the CLI, supply the actor flag or ensure the environment provides an identity.
  3. Use errors.Is(err, issueops.ErrValidation) to detect and surface a clear 'actor required' message to users.
  4. If identity is genuinely unavailable, decide on an explicit service account name rather than an empty string.

Example fix

// before
plan, err := PlanCompareAndSetKey(issueops.CompareAndSetKeyRequest{IssueID: "bd-1", Key: "status"})
// after
plan, err := PlanCompareAndSetKey(issueops.CompareAndSetKeyRequest{Actor: "alice", IssueID: "bd-1", Key: "status"})
Defensive patterns

Strategy: validation

Validate before calling

if req.Actor == "" {
    return errors.New("compare-and-set requires an actor; set Actor to the issuing identity")
} // run before PlanCompareAndSetKey

Try / catch

plan, err := PlanCompareAndSetKey(req)
if err != nil {
    if errors.Is(err, issueops.ErrValidation) && strings.Contains(err.Error(), "requires an actor") {
        return fmt.Errorf("actor required for compare-and-set: configure identity")
    }
    return err
}

Prevention

When it happens

Trigger: Calling PlanCompareAndSetKey with a CompareAndSetKeyRequest whose Actor field is "" — e.g. building the request programmatically without the current user, or a CLI context where the actor was not resolved before planning.

Common situations: Automation/CI running without a configured user identity; forgetting to pass --actor or equivalent on the CLI; constructing the request in tests or scripts with only key/value fields filled.

Related errors


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