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
- Populate both IssueID and DependsOnID for every edge before calling.
- Verify the referenced issues exist and their IDs were resolved correctly upstream.
- Filter out edges with blank endpoints before constructing the request.
- 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
- Validate all edges before constructing the request.
- Resolve issue IDs via lookup, never trust raw parsed input.
- Drop edges with blank endpoints early with a visible warning.
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
- no store is open for this workspace
- no absolute native user directory is available
- ExternalDoltConfig: set either Socket OR (Host, Port), not b
- ExternalDoltConfig: must set Socket or (Host, Port)
- ExternalDoltConfig: Host requires Port
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/0a0f50cd4666bece.
Report an issue: GitHub.