gastownhall/beads · warning · storage.ErrValidation

%w: add dependencies requires an actor

Error message

%w: add dependencies requires an actor

What it means

ValidateAddDependenciesRequest enforces that every dependency-creation request names the actor performing the change, because beads records who added each dependency for audit and permission purposes. When request.Actor is empty the request is rejected, wrapped with storage.ErrValidation so callers can errors.Is() it. This is a pure validation error raised before any database work.

Source

Thrown at internal/storage/issueops/dependency_editor.go:30

)

// ValidateAddDependenciesRequest applies the request rules every
// DependencyEditor implementation shares. It lives here rather than in each of
// them because a rule enforced on one backend and not the other is not a
// contract.
//
// The self-dependency refusal is here, ahead of the per-edge cycle probe and
// for EVERY edge type, for the reason the domain path states: a blocking
// self-edge otherwise trips the cycle check and reports the wrong refusal, and
// SkipPerEdgeCycleCheck would skip it entirely.
//
// The type check is that there IS a type — non-empty, within the column's
// length. It is deliberately not a membership test: the vocabulary is an open,
// workspace-configurable set (see the Dep* constants), so refusing an unlisted
// type would refuse a workspace's own.
func ValidateAddDependenciesRequest(request publicops.AddDependenciesRequest) error {
	if request.Actor == "" {
		return fmt.Errorf("%w: add dependencies requires an actor", storage.ErrValidation)
	}
	if len(request.Edges) == 0 {
		return fmt.Errorf("%w: add dependencies requires at least one edge", storage.ErrValidation)
	}
	for i, edge := range request.Edges {
		if edge.IssueID == "" || edge.DependsOnID == "" {
			return fmt.Errorf("%w: add dependencies edge %d requires both endpoints", storage.ErrValidation, i)
		}
		if !edge.Type.IsValid() {
			return fmt.Errorf("%w: add dependencies edge %d requires a dependency type (max %d chars)",
				storage.ErrValidation, i, types.MaxDependencyTypeLen)
		}
		if edge.IssueID == edge.DependsOnID {
			return fmt.Errorf("%w: %s cannot depend on itself", domain.ErrSelfDependency, edge.IssueID)
		}
	}
	return nil
}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Set request.Actor to the active identity (configured user, agent name, or authenticated principal).
  2. Configure the identity source (e.g. bd config / env) so callers always have an actor available.
  3. Reject empty actors at your own boundary before invoking the storage layer.
  4. Check errors.Is(err, storage.ErrValidation) to confirm it's this validation, not a storage failure.

Example fix

// before
req := publicops.AddDependenciesRequest{Edges: edges}
// after
req := publicops.AddDependenciesRequest{Actor: "agent:claude", Edges: edges}
Defensive patterns

Strategy: validation

Validate before calling

if req.Actor == "" {
    return fmt.Errorf("actor must be set (e.g. \"agent:claude\") before calling AddDependencies")
}

Type guard

func hasActor(req publicops.AddDependenciesRequest) bool { return req.Actor != "" }

Try / catch

err := store.AddDependencies(ctx, req)
if errors.Is(err, storage.ErrValidation) {
    // re-prompt for identity or fix request construction
}

Prevention

When it happens

Trigger: Calling AddDependencies (or building a publicops.AddDependenciesRequest) with Actor left as the zero value "" — e.g. CLI/agent code that didn't capture the current user identity.

Common situations: Automated scripts constructing requests manually without setting Actor; refactors that removed the actor plumbing; sessions with no configured user identity.

Related errors


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