gastownhall/beads · error · publicops.ErrValidation

direction %q must be one of %q, %q, %q

Error message

direction %q must be one of %q, %q, %q

What it means

ValidateWalkTreeRequest rejects a WalkDependencyTreeRequest whose Direction field is not one of the three supported traversal directions (down, up, both). The library throws this wrapped in publicops.ErrValidation because the caller supplied an unrecognized direction string, likely a typo or a value copied from a different API. An empty direction is silently defaulted to TreeDown, so only non-empty invalid values reach this error.

Source

Thrown at internal/storage/issueops/tree_walk.go:36

// needs a transaction because the root probe, the recursion and the hydration
// must see one database state, and for a `both` walk that covers BOTH
// directions.

// ValidateWalkTreeRequest checks a walk request against the vocabulary
// issueops.WalkTreeRequest documents and returns the normalized direction. It is
// pure and shared so that all three backends refuse in the same words.
func ValidateWalkTreeRequest(req publicops.WalkTreeRequest) (publicops.TreeDirection, error) {
	if req.RootID == "" {
		return "", fmt.Errorf("%w: root id must not be empty", publicops.ErrValidation)
	}
	direction := req.Direction
	if direction == "" {
		direction = publicops.TreeDown
	}
	switch direction {
	case publicops.TreeDown, publicops.TreeUp, publicops.TreeBoth:
	default:
		return "", fmt.Errorf("%w: direction %q must be one of %q, %q, %q",
			publicops.ErrValidation, req.Direction,
			publicops.TreeDown, publicops.TreeUp, publicops.TreeBoth)
	}
	if req.MaxDepth < 1 {
		return "", fmt.Errorf("%w: max depth must be at least 1, got %d", publicops.ErrValidation, req.MaxDepth)
	}
	if req.MaxRows < 0 {
		return "", fmt.Errorf("%w: max rows must not be negative, got %d", publicops.ErrValidation, req.MaxRows)
	}
	return direction, nil
}

// PruneTreeByStatus keeps every node carrying status, plus the ancestor chain of
// each survivor, in the walk order the nodes arrived in.
//
// KEEPING THE ANCESTORS IS WHAT MAKES THE ANSWER STILL A TREE. A bare filter
// would return nodes whose ParentID names something absent from the answer, and
// every renderer that rebuilds the shape from Depth and ParentID would then draw

View on GitHub (pinned to 71377f2769)

Solutions

  1. Set req.Direction to one of publicops.TreeDown, publicops.TreeUp, or publicops.TreeBoth.
  2. Leave req.Direction empty ("") to accept the default (TreeDown).
  3. Map/normalize user input against the three constants before constructing the request.
  4. Check that you are not importing similarly-named constants from a different package or an outdated version.

Example fix

// before
req := publicops.WalkDependencyTreeRequest{IssueID: id, Direction: "down"}
// after
req := publicops.WalkDependencyTreeRequest{IssueID: id, Direction: publicops.TreeDown}
Defensive patterns

Strategy: validation

Validate before calling

func validDirection(d string) bool {
	return d == "" || d == publicops.TreeDown || d == publicops.TreeUp || d == publicops.TreeBoth
}
if !validDirection(req.Direction) {
	return fmt.Errorf("direction %q not supported", req.Direction)
}

Try / catch

if err := walk(ctx, req); errors.Is(err, publicops.ErrValidation) {
	// fix request fields; do not retry
}

Prevention

When it happens

Trigger: Calling WalkDependencyTreeInTx (directly or via WalkDependencyTree) with req.Direction set to any string other than publicops.TreeDown, TreeUp, or TreeBoth, e.g. "downward", "children", "D", or a stale constant from an older SDK version.

Common situations: Hand-building the request struct instead of using helpers; renaming between API versions where direction constants changed; user-supplied CLI input passed straight into Direction without mapping.

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