gastownhall/beads · warning · storage.ErrValidation

%w: add dependencies edge %d requires both endpoints

Error message

%w: add dependencies edge %d requires both endpoints

What it means

Every dependency edge must have both endpoints set: the IssueID (the issue that depends) and the DependsOnID (the issue it depends on). When either is empty, the validator rejects the request with this message including the edge's index, wrapped with storage.ErrValidation. This catches malformed edges before they reach the database.

Source

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

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

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

View on GitHub (pinned to 71377f2769)

Solutions

  1. Populate both IssueID and DependsOnID for every edge before calling.
  2. Verify the referenced issues exist and their IDs were resolved correctly upstream.
  3. Filter out edges with blank endpoints before constructing the request.
  4. Use the reported index in the message to find the offending edge in your batch.

Example fix

// before
edges := []publicops.DependencyEdge{{IssueID: id}} // DependsOnID missing
// after
edges := []publicops.DependencyEdge{{IssueID: id, DependsOnID: depID, Type: types.DepBlocks}}
Defensive patterns

Strategy: validation

Validate before calling

for i, e := range req.Edges {
    if e.IssueID == "" || e.DependsOnID == "" {
        return fmt.Errorf("edge %d missing endpoint", i)
    }
}

Type guard

func edgeComplete(e publicops.DependencyEdge) bool {
    return e.IssueID != "" && e.DependsOnID != ""
}

Try / catch

err := store.AddDependencies(ctx, req)
if errors.Is(err, storage.ErrValidation) {
    // message names the offending edge index; drop/fix that edge and retry
}

Prevention

When it happens

Trigger: Calling AddDependencies with an edge where Edge.IssueID == "" or Edge.DependsOnID == "" — e.g. parsing dependency output where one side of the pair was missing or a struct literal omitted a field.

Common situations: Importing dependencies from external data files with missing IDs; lookups that returned empty IDs for deleted/unknown issues; hand-written migrations building edges programmatically.

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/0a0f50cd4666bece. Report an issue: GitHub.