gastownhall/beads · error
cascade traversal discovered over %d issues; aborting to pre
Error message
cascade traversal discovered over %d issues; aborting to prevent runaway deletion
What it means
FindAllDependentsInTx performs a breadth-first traversal of the dependency graph to find everything that would be cascade-deleted. If the traversal discovers more than maxRecursiveResults distinct issues, it aborts with this error to prevent a runaway (possibly cyclic or hub-dependent) cascade from deleting a huge portion of the database.
Source
Thrown at internal/storage/issueops/delete.go:417
}
// findAllDependentsRecursiveInTx finds all issues that depend on the given
// issues, recursively. Uses batched IN-clause queries. Traversal is capped
// at maxRecursiveResults total discovered IDs.
//
//nolint:gosec // G201: inClause contains only ? placeholders
func FindAllDependentsInTx(ctx context.Context, tx DBTX, ids []string) (map[string]bool, error) {
result := make(map[string]bool)
for _, id := range ids {
result[id] = true
}
toProcess := make([]string, len(ids))
copy(toProcess, ids)
for len(toProcess) > 0 {
if len(result) > maxRecursiveResults {
return nil, fmt.Errorf("cascade traversal discovered over %d issues; aborting to prevent runaway deletion", maxRecursiveResults)
}
batchEnd := deleteBatchSize
if batchEnd > len(toProcess) {
batchEnd = len(toProcess)
}
batch := toProcess[:batchEnd]
toProcess = toProcess[batchEnd:]
inClause, args := buildSQLInClause(batch)
for _, depTable := range []string{"dependencies", "wisp_dependencies"} {
rows, err := tx.QueryContext(ctx,
fmt.Sprintf(`SELECT issue_id FROM %s WHERE %s`, depTable, depTargetIn("", inClause)),
args...)
if err != nil {
if optionalBlockedTable(depTable) && isTableNotExistError(err) {
continue
}
return nil, fmt.Errorf("query dependents for batch from %s: %w", depTable, err)View on GitHub (pinned to 71377f2769)
Solutions
- Delete in smaller pieces — delete leaf dependents first instead of one big cascade
- Inspect the dependency graph (bd dep tree / bd blocked) to find the hub or cycle and break it
- If the set is legitimately large and intended, delete in batches scoped by explicit ID lists
- Report/fix the cycle if one exists; cycles should not occur in a healthy DAG
Example fix
// before bd delete bd-1 --cascade // aborts: >maxRecursiveResults // after — prune from the leaves bd dep tree bd-1 # inspect scope bd delete bd-101 bd-102 --cascade bd delete bd-1 --cascade
Defensive patterns
Strategy: validation
Validate before calling
// pre-check cascade size before attempting delete
func cascadeSize(ctx, tx, ids) int {
// count reachable dependents via bd dep tree or iterative queries
}
// if cascadeSize > maxRecursiveResults { delete in smaller batches instead } Try / catch
_, err := DeleteIssuesInTx(ctx, tx, ids, WithCascade())
if err != nil && strings.Contains(err.Error(), "aborting to prevent runaway deletion") {
// fall back to scoped deletes
return deleteInChunks(ctx, ids)
}
return err Prevention
- Inspect bd dep tree before --cascade on hub/root issues
- Find and fix dependency cycles in the graph
- Delete large subtrees incrementally from the leaves
- Keep the dependency graph a healthy DAG
When it happens
Trigger: ResolveDeletionSetInTx invoked with --cascade on an issue that transitively depends on more than maxRecursiveResults issues — e.g. deleting a root blocking hundreds of chained beads, or a dependency cycle inflating the reachable set.
Common situations: Deleting a top-level epic/milestone whose entire subtree exceeds the cap; accidental cycles in dependencies making the reachable set appear unbounded; importing a dependency graph with a hub issue everything depends on.
Related errors
- delete: cascade expansion: %w
- remove dep %s -> %s: %w
- expand cascade: %w
- query dependents for batch from %s: %w
- delete: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/57d7ab09c89135ef.
Report an issue: GitHub.