gastownhall/beads · warning · publicops.ErrValidation
root id must not be empty
Error message
root id must not be empty
What it means
ValidateWalkTreeRequest is the pure, shared validation behind WalkDependencyTreeInTx on all backends; it refuses a WalkTreeRequest whose RootID is empty, wrapping publicops.ErrValidation with 'root id must not be empty'. This is a programmer/caller input error, never a database condition — the check runs before any transaction is opened.
Source
Thrown at internal/storage/issueops/tree_walk.go:27
)
// The dependency-tree WALK: the shared body behind issueops.TreeWalker on all
// three backends, split into a pure half and a transactional half so the parts
// that decide what the answer MEANS are testable without a database.
//
// ValidateWalkTreeRequest holds the request vocabulary, PruneTreeByStatus the
// ancestor-keeping rule and MergeBidirectionalTree the two-walk concatenation;
// all three are pure and pinned in tree_walk_test.go. WalkDependencyTreeInTx
// 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)
}View on GitHub (pinned to 71377f2769)
Solutions
- Set req.RootID to a valid issue ID before calling the walk API.
- Use errors.Is(err, publicops.ErrValidation) to detect this and other validation refusals and surface a user-facing 'issue ID required' message.
- For CLI paths, validate the ID argument is non-empty before constructing the request.
- If the ID comes from a prior lookup, check that lookup's error first — an empty ID often masks an earlier failure.
Example fix
// before
req := publicops.WalkTreeRequest{Direction: publicops.TreeDown}
dir, err := issueops.ValidateWalkTreeRequest(req) // error: root id must not be empty
// after
if rootID == "" { return fmt.Errorf("issue ID required") }
req := publicops.WalkTreeRequest{RootID: rootID, Direction: publicops.TreeDown}
dir, err := issueops.ValidateWalkTreeRequest(req) Defensive patterns
Strategy: validation
Validate before calling
func validWalkRequest(req issueops.WalkTreeRequest) error {
if req.RootID == "" { return fmt.Errorf("root id required") }
return nil
} Type guard
func hasRootID(req issueops.WalkTreeRequest) bool {
return strings.TrimSpace(req.RootID) != ""
} Try / catch
dir, err := issueops.ValidateWalkTreeRequest(req)
if err != nil {
if errors.Is(err, publicops.ErrValidation) {
return fmt.Errorf("invalid tree walk request: %w", err)
}
return err
} Prevention
- Check the ID argument is non-empty before building WalkTreeRequest
- Propagate errors from prior lookups instead of passing a zero-value ID
- Rely on errors.Is(err, ErrValidation) to classify all walk-validation refusals
- Validate JSON-deserialized requests for required fields (root_id, MaxDepth >= 1)
When it happens
Trigger: Calling WalkDependencyTreeInTx (or the TreeWalker API) with publicops.WalkTreeRequest{RootID: ""} — e.g. an unset variable, a struct built from an empty CLI argument, or an ID field that was never populated.
Common situations: Scripting `bd dep tree` where the ID argument is missing; passing an issue struct's ID straight through when the issue lookup failed earlier; deserializing a request from JSON that omitted root_id; copy-pasted code paths that forget to set RootID.
Understand the failure class
Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.
Related errors
- invalid issue type: %s
- invalid priority %q (expected 0-4 or P0-P4, not words like h
- invalid ID format '%s' (expected format: prefix-hash or pref
- no store is open for this workspace
- not found
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/7577fb48275e96aa.
Report an issue: GitHub.