gastownhall/beads · error · storage.ErrValidation

%w: read edges type %d is not a usable dependency type (non-

Error message

%w: read edges type %d is not a usable dependency type (non-empty, max %d chars)

What it means

ValidateEdgeReadRequest rejects an EdgeReadRequest whose Types slice contains a dependency type that fails types.DependencyType.IsValid() — empty or exceeding types.MaxDependencyTypeLen. Such a type would act as a filter silently matching no edges, so it is refused instead. The error wraps storage.ErrValidation.

Source

Thrown at internal/storage/issueops/edges.go:27

	"github.com/steveyegge/beads/internal/storage"
	"github.com/steveyegge/beads/internal/types"
	publicops "github.com/steveyegge/beads/issueops"
)

// ValidateEdgeReadRequest applies the request rules every EdgeReader
// implementation shares. Both tell a caller's mistake from a legitimately empty
// answer: an empty ID entry names nothing, and an unusable dependency type would
// become a filter that silently matches nothing. An empty ID SLICE is neither —
// it asks about no anchors and gets none back.
func ValidateEdgeReadRequest(request publicops.EdgeReadRequest) error {
	for i, id := range request.IDs {
		if id == "" {
			return fmt.Errorf("%w: read edges id %d is empty", storage.ErrValidation, i)
		}
	}
	for i, depType := range request.Types {
		if !depType.IsValid() {
			return fmt.Errorf("%w: read edges type %d is not a usable dependency type (non-empty, max %d chars)",
				storage.ErrValidation, i, types.MaxDependencyTypeLen)
		}
	}
	return nil
}

// EdgeReadAnchors is the de-duplicated anchor list a read runs against: the
// request's ids with repeats collapsed onto their first mention.
//
// It is shared rather than a loop in each implementation because the
// de-duplication decides the SHAPE of the answer — one entry per distinct id, in
// first-mention order. BlockingAnnotator makes the same promise over the same
// shape of request (blocking_annotation.go) and reaches it here too.
func EdgeReadAnchors(ids []string) []string {
	seen := make(map[string]struct{}, len(ids))
	out := make([]string, 0, len(ids))
	for _, id := range ids {
		if _, dup := seen[id]; dup {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Call types.DependencyType.IsValid() on each filter before building the request.
  2. Trim whitespace and enforce MaxDependencyTypeLen at the input boundary.
  3. Use the exported dependency-type constants instead of ad-hoc strings.
  4. Update callers to current type names after upgrades.

Example fix

// before
req := publicops.EdgeReadRequest{IDs: ids, Types: []types.DependencyType{t}}
// after
if !t.IsValid() { return fmt.Errorf("bad dependency type %q", t) }
req := publicops.EdgeReadRequest{IDs: ids, Types: []types.DependencyType{t}}
Defensive patterns

Strategy: validation

Validate before calling

func validTypes(ts []types.DependencyType) bool {
	for _, t := range ts { if !t.IsValid() { return false } }
	return true
}

Type guard

func filterValidTypes(ts []types.DependencyType) []types.DependencyType {
	return slices.DeleteFunc(slices.Clone(ts), func(t types.DependencyType) bool { return !t.IsValid() })
}

Try / catch

if err := ValidateEdgeReadRequest(req); err != nil {
	if errors.Is(err, storage.ErrValidation) && strings.Contains(err.Error(), "dependency type") {
		req.Types = filterValidTypes(req.Types)
	}
}

Prevention

When it happens

Trigger: ReadEdges / ExecuteEdgeRead called with Types containing "" or an over-long string; types.DependencyType built from raw user input or config values without validation.

Common situations: Typo'd or renamed dependency types in flags/config; a type with trailing whitespace exceeding the length limit; older code using a type name removed or shortened in a newer beads release.

Related errors


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