gastownhall/beads · error · storage.ErrValidation

ErrValidation

ErrValidation

Error message

%w: update requires actor and issue ID

What it means

ExecuteUpdate applies a guarded issue update inside a transaction and requires an explicit Actor and IssueID on the request. When either is empty it returns storage.ErrValidation wrapped with this message, before any validation or database work runs. This forces audit-attributable, explicitly targeted updates.

Source

Thrown at internal/storage/issueops/execution.go:135

// skippedDependencyError refuses a guarded create whose requested edges were
// not all written. The batch engine drops a dangling edge so a partial import
// still lands, but a guarded create that reported success while silently
// discarding a parent, waits-for, or explicit dependency is data loss: the
// caller has no way to learn the relationship is missing. Refusing rolls the
// whole create back with the enclosing transaction.
func skippedDependencyError(skipped []skippedDependency) error {
	edges := make([]string, 0, len(skipped))
	for _, edge := range skipped {
		edges = append(edges, fmt.Sprintf("%s -> %s (%s)", edge.issueID, edge.dependsOnID, edge.reason))
	}
	return fmt.Errorf("create: dependencies could not be created: %s: %w", strings.Join(edges, "; "), storage.ErrNotFound)
}

// ExecuteUpdate applies a guarded update in tx and reports durable tables changed.
func ExecuteUpdate(ctx context.Context, tx *sql.Tx, request publicops.UpdateRequest) (publicops.UpdateResult, ChangedTables, error) {
	attempt := CloneUpdateRequest(request)
	if attempt.Actor == "" || attempt.IssueID == "" {
		return publicops.UpdateResult{}, nil, fmt.Errorf("%w: update requires actor and issue ID", storage.ErrValidation)
	}
	if err := ValidateUpdateRequest(attempt); err != nil {
		return publicops.UpdateResult{}, nil, err
	}
	if err := ValidateMetadataPatch(attempt.Patch.Metadata); err != nil {
		return publicops.UpdateResult{}, nil, err
	}
	// The plane restriction is resolved HERE, inside the update's own
	// transaction, so a caller that serves durable issues only cannot be handed
	// a wisp by a resolve that ran earlier.
	if attempt.IssuePlaneOnly && IsActiveWispInTx(ctx, tx, attempt.IssueID) {
		return publicops.UpdateResult{}, nil, fmt.Errorf("%w: issue %s", storage.ErrNotFound, attempt.IssueID)
	}
	tables := ChangedTables{}
	before, err := GetIssueInTx(ctx, tx, attempt.IssueID)
	if err != nil {
		return publicops.UpdateResult{}, nil, err
	}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Set Actor on the UpdateRequest (e.g. the CLI username or service identity) before calling.
  2. Set IssueID to the target issue's ID (bd-xxxx).
  3. If building requests from flags, default Actor from config/env at the CLI boundary.
  4. Run storage.ValidateUpdateRequest-style checks client-side before invoking.

Example fix

// before
req := publicops.UpdateRequest{IssueID: "bd-42", Patch: patch}
// after
req := publicops.UpdateRequest{IssueID: "bd-42", Actor: "agent", Patch: patch}
Defensive patterns

Strategy: validation

Validate before calling

func validUpdate(r publicops.UpdateRequest) error {
	if r.Actor == "" {
		return fmt.Errorf("update requires actor")
	}
	if r.IssueID == "" {
		return fmt.Errorf("update requires issue ID")
	}
	return nil
}

Try / catch

res, _, err := ExecuteUpdate(ctx, tx, req)
if errors.Is(err, storage.ErrValidation) {
	return fmt.Errorf("request rejected: %w (set Actor and IssueID)", err)
}

Prevention

When it happens

Trigger: Calling ExecuteUpdate (directly or via applyUpdate / spliceMetadataRefs) with a publicops.UpdateRequest where Actor == "" or IssueID == "".

Common situations: Programmatic updates that set Patch fields but forget Actor; constructing UpdateRequest from CLI flags where the actor flag was omitted; ID left empty when the caller expected auto-detection from context.

Understand the failure class

Background: "Missing required field" and "field is required" errors: why libraries reject payloads that omit mandatory fields — this error's family across 20 libraries.

Related errors


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