gastownhall/beads · warning

wisp cascade traversal discovered over %d issues; aborting

Error message

wisp cascade traversal discovered over %d issues; aborting

What it means

FindWispDependentsRecursiveInTx performs a breadth-first traversal of wisp_dependencies and refuses to continue once the discovered set exceeds maxResults. This is a deliberate safety guard against runaway cascades (cycles or very large dependency graphs); it aborts with this error rather than exhausting memory or scanning the whole table.

Source

Thrown at internal/storage/issueops/bulk_ops.go:374

	if len(ids) == 0 {
		return nil, nil
	}

	const maxResults = 10000
	const batchSize = 50

	seen := make(map[string]bool, len(ids))
	for _, id := range ids {
		seen[id] = true
	}

	toProcess := make([]string, len(ids))
	copy(toProcess, ids)
	discovered := make(map[string]bool)

	for len(toProcess) > 0 {
		if len(seen) > maxResults {
			return discovered, fmt.Errorf("wisp cascade traversal discovered over %d issues; aborting", maxResults)
		}

		end := batchSize
		if end > len(toProcess) {
			end = len(toProcess)
		}
		batch := toProcess[:end]
		toProcess = toProcess[end:]

		placeholders, args := buildSQLInClause(batch)
		rows, err := tx.QueryContext(ctx,
			fmt.Sprintf(`SELECT issue_id FROM wisp_dependencies WHERE %s IN (%s)`, DepTargetExpr, placeholders),
			args...)
		if err != nil {
			return discovered, fmt.Errorf("query wisp dependents: %w", err)
		}

		for rows.Next() {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Increase maxResults to a value covering the expected cascade size and retry.
  2. Break the traversal into per-branch calls with smaller roots to stay under the limit.
  3. Inspect the dependency graph for accidental cycles and remove them.
  4. Handle the partial `discovered` set (it is returned with the error) to triage before re-running with a bigger bound.

Example fix

// before
ids, err := store.FindWispDependentsRecursive(ctx, tx, rootID, 100) // aborts: over 100 issues
// after
ids, err := store.FindWispDependentsRecursive(ctx, tx, rootID, 10000)
Defensive patterns

Strategy: validation

Validate before calling

// size the bound to your graph before traversal
const safeMax = 50000
ids, err := store.FindWispDependentsRecursive(ctx, tx, rootID, safeMax)
if err != nil && strings.Contains(err.Error(), "cascade traversal discovered over") {
    return fmt.Errorf("cascade too large; triage partial set and raise bound")
}

Try / catch

ids, err := store.FindWispDependentsRecursive(ctx, tx, rootID, maxResults)
if err != nil && strings.Contains(err.Error(), "aborting") {
    partial := ids // returned despite error; triage before retrying with a bigger bound
    return escalateCascap(partial, err)
}

Prevention

When it happens

Trigger: Calling FindWispDependentsRecursiveInTx with an ID whose transitive dependent closure exceeds maxResults — a hub wisp that thousands of others depend on, or a dependency cycle feeding back into already-visited nodes at scale.

Common situations: Bulk-deleting or cascading a top-level wisp in a large project; accidental dependency cycle creating unbounded traversal; calling with a very low maxResults while the graph legitimately grew.

Related errors


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