gastownhall/beads · error · issueops.ErrValidation
%w: compare-and-set requires an issue id
Error message
%w: compare-and-set requires an issue id
What it means
PlanCompareAndSetKey returns this wrapped issueops.ErrValidation error when in.IssueID is empty. The compare-and-set operation targets a specific issue's metadata key, so an issue id is mandatory for the plan. It is a pure input-validation error raised before any storage access.
Source
Thrown at internal/storage/metadata_cas.go:64
// 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)
}
return plan, nil
}
View on GitHub (pinned to 71377f2769)
Solutions
- Set in.IssueID to the target issue's id before calling (e.g. "bd-42").
- Guard the caller: if issueID == "" return early with a clearer upstream message instead of reaching the planner.
- In shell scripts, quote and check: : "${ID:?issue id required}".
- Match errors.Is(err, issueops.ErrValidation) to render a user-friendly 'issue id required' error.
Example fix
// before
plan, err := PlanCompareAndSetKey(issueops.CompareAndSetKeyRequest{Actor: "alice", Key: "status"})
// after
plan, err := PlanCompareAndSetKey(issueops.CompareAndSetKeyRequest{Actor: "alice", IssueID: "bd-42", Key: "status"}) Defensive patterns
Strategy: validation
Validate before calling
if req.IssueID == "" {
return errors.New("compare-and-set requires an issue id")
} // run before PlanCompareAndSetKey Try / catch
plan, err := PlanCompareAndSetKey(req)
if err != nil {
if errors.Is(err, issueops.ErrValidation) && strings.Contains(err.Error(), "requires an issue id") {
return fmt.Errorf("no issue selected: resolve an issue id first")
}
return err
} Prevention
- Fail fast in the caller when issue lookup returns zero results instead of passing "".
- In shell scripts, use ${ID:?message} to catch unset variables.
- Keep issue-id resolution and mutation planning in the same code path.
When it happens
Trigger: Calling PlanCompareAndSetKey with CompareAndSetKeyRequest.IssueID == "" — e.g. the caller resolved no issue from a search, or a variable holding the issue id was never assigned.
Common situations: A lookup step returned zero results and the empty string was passed through; scripts using an unset shell variable ($ID expands to empty); refactors that renamed the field and left it unset.
Related errors
- %w: compare-and-set requires an actor to attribute the swap
- %w: %v
- %w: expected value for metadata key %q: %v
- %w: new value for metadata key %q: %v
- ExternalDoltConfig: must set Socket or (Host, Port)
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/97f52cae7ca952e2.
Report an issue: GitHub.