gastownhall/beads · warning · storage.ErrValidation

%w: add dependencies edge %d requires a dependency type (max

Error message

%w: add dependencies edge %d requires a dependency type (max %d chars)

What it means

Each dependency edge must carry a valid dependency type (edge.Type.IsValid()) — non-empty and no longer than types.MaxDependencyTypeLen. Beads deliberately treats the type vocabulary as open (workspace-configurable), so the check is only for presence/length, not membership in a fixed list. Failures are wrapped with storage.ErrValidation.

Source

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

// 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
}

// ValidateRemoveDependencyRequest applies the request rules every
// DependencyEditor implementation shares for a removal.
func ValidateRemoveDependencyRequest(request publicops.RemoveDependencyRequest) error {
	if request.Actor == "" {
		return fmt.Errorf("%w: remove dependency requires an actor", storage.ErrValidation)
	}
	if request.IssueID == "" || request.DependsOnID == "" {
		return fmt.Errorf("%w: remove dependency requires both endpoints", storage.ErrValidation)
	}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Set a known type constant (e.g. types.DepBlocks) or a valid workspace-configured type on every edge.
  2. Truncate or reject custom type names longer than types.MaxDependencyTypeLen before calling.
  3. Default empty types to the canonical value your workflow expects.
  4. Match errors.Is(err, storage.ErrValidation) to confirm it's this check.

Example fix

// before
edge := publicops.DependencyEdge{IssueID: a, DependsOnID: b} // Type empty
// after
edge := publicops.DependencyEdge{IssueID: a, DependsOnID: b, Type: types.DepBlocks}
Defensive patterns

Strategy: validation

Validate before calling

for i, e := range req.Edges {
    if !e.Type.IsValid() {
        return fmt.Errorf("edge %d has invalid type %q", i, e.Type)
    }
}

Type guard

func validEdgeType(t types.DependencyType) bool {
    return t != "" && len(t) <= types.MaxDependencyTypeLen
}

Try / catch

err := store.AddDependencies(ctx, req)
if errors.Is(err, storage.ErrValidation) {
    // fix the type (set a known constant or truncate) and retry
}

Prevention

When it happens

Trigger: Calling AddDependencies with an edge whose Type is the zero value ("") or exceeds MaxDependencyTypeLen characters — e.g. free-form type strings from user input that weren't truncated.

Common situations: Import scripts passing arbitrary dependency labels without validating length; forgetting to default the type when constructing edges manually; schema changes that introduced longer custom types.

Understand the failure class

Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.

Related errors


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