gastownhall/beads · error · issueops.ErrValidation
%w: apply batch requires an actor
Error message
%w: apply batch requires an actor
What it means
PlanApplyBatch requires a non-empty Actor because every planned mutation must be attributable for audit/history. An empty Actor is the first request-shape check and fails before any item validation, wrapped with issueops.ErrValidation so callers can errors.Is on it.
Source
Thrown at internal/storage/batch_apply.go:71
Items []issueops.ApplyItem
// KeyIndex maps each create item's Key to the index that declares it. The
// body resolves refs through it and the wire half reports an unknown key
// from it, so the two cannot disagree about what a key means.
KeyIndex map[string]int
}
// PlanApplyBatch validates an apply-batch request and normalizes its waits-for
// gate metadata. 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.
//
// THE ORDER OF THE CHECKS IS PART OF THE CONTRACT, because a request can be
// wrong in several ways at once and a caller fixing them one at a time needs
// the same answer every time. Request-level shape first, then per-item shape,
// then the ref graph, then the guards.
func PlanApplyBatch(in issueops.ApplyBatchRequest) (ApplyBatchPlan, error) {
if in.Actor == "" {
return ApplyBatchPlan{}, fmt.Errorf("%w: apply batch requires an actor", issueops.ErrValidation)
}
if len(in.Items) == 0 {
return ApplyBatchPlan{}, fmt.Errorf("%w: apply batch requires at least one item", issueops.ErrValidation)
}
if len(in.Items) > issueops.MaxApplyBatchItems {
return ApplyBatchPlan{}, fmt.Errorf("%w: apply batch accepts at most %d items, got %d",
issueops.ErrValidation, issueops.MaxApplyBatchItems, len(in.Items))
}
keyIndex, err := planApplyBatchKeys(in.Items)
if err != nil {
return ApplyBatchPlan{}, err
}
plan := ApplyBatchPlan{
Actor: in.Actor,
Provenance: in.Provenance,
ForceIDPrefix: in.ForceIDPrefix,View on GitHub (pinned to 71377f2769)
Solutions
- Set Actor to the acting user/bot identity before calling PlanApplyBatch.
- If the caller is a service, propagate the authenticated identity into the request instead of leaving it zero.
- Handle the error with errors.Is(err, issueops.ErrValidation) to distinguish validation failures from storage errors.
Example fix
// before
plan, err := PlanApplyBatch(issueops.ApplyBatchRequest{Items: items})
// after
plan, err := PlanApplyBatch(issueops.ApplyBatchRequest{Actor: "agent:ci-bot", Items: items}) Defensive patterns
Strategy: validation
Validate before calling
if req.Actor == "" {
return fmt.Errorf("actor is required for batch apply")
} Try / catch
_, err := storage.PlanApplyBatch(req)
if errors.Is(err, issueops.ErrValidation) {
return fmt.Errorf("invalid batch request: %w", err)
} Prevention
- Propagate the authenticated identity into every request struct.
- Avoid zero-value ApplyBatchRequest literals; use a constructor that requires actor.
- Add a lint/test asserting Actor is set in all batch call sites.
When it happens
Trigger: Calling PlanApplyBatch with issueops.ApplyBatchRequest{Actor: ""} — e.g. agent/CLI code that never set the acting user, or a struct literal omitting the Actor field.
Common situations: Headless scripts or bots constructing the request programmatically and forgetting the actor; refactors that renamed/dropped the Actor field; tests reusing a zero-value request struct.
Related errors
- %w: apply batch requires at least one item
- %w: apply batch accepts at most %d items, got %d
- %w: apply batch item %d must carry exactly one payload, got
- %w: apply batch item %d has unknown kind %q
- ErrValidation
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/497369fd71fe7c84.
Report an issue: GitHub.