gastownhall/beads · error

inline expansion depth limit exceeded: max %d levels

Error message

inline expansion depth limit exceeded: max %d levels

What it means

This error is thrown by applyInlineExpansionsRecursive in internal/formula/expand.go when processing inline step expansions (steps with an Expand field) recurses deeper than DefaultMaxExpansionDepth levels. The library imposes a hard recursion cap so a formula whose expansion references itself (directly or transitively) cannot loop forever. It typically indicates a cycle in the Expand references among expansion formulas.

Source

Thrown at internal/formula/expand.go:461

//
// This differs from compose.Expand in that the expansion is declared inline on the
// step itself rather than in a central compose section.
//
// Returns a new steps slice with inline expansions applied.
// The original steps slice is not modified.
func ApplyInlineExpansions(steps []*Step, parser *Parser) ([]*Step, error) {
	if parser == nil {
		return steps, nil
	}

	return applyInlineExpansionsRecursive(steps, parser, 0)
}

// applyInlineExpansionsRecursive handles inline expansions for a slice of steps.
// depth tracks recursion to prevent infinite expansion loops.
func applyInlineExpansionsRecursive(steps []*Step, parser *Parser, depth int) ([]*Step, error) {
	if depth > DefaultMaxExpansionDepth {
		return nil, fmt.Errorf("inline expansion depth limit exceeded: max %d levels", DefaultMaxExpansionDepth)
	}

	var result []*Step

	for _, step := range steps {
		// Check if this step has an inline expansion
		if step.Expand != "" {
			// Load the expansion formula
			expFormula, err := parser.LoadByName(step.Expand)
			if err != nil {
				return nil, fmt.Errorf("inline expand on step %q: loading %q: %w", step.ID, step.Expand, err)
			}

			if expFormula.Type != TypeExpansion {
				return nil, fmt.Errorf("inline expand on step %q: %q is not an expansion formula (type=%s)",
					step.ID, step.Expand, expFormula.Type)
			}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Inspect the Expand fields along the chain and break the cycle (remove the self/back reference).
  2. Flatten overly deep nesting by inlining some template steps directly instead of chaining Expand references.
  3. If legitimately deep nesting is required, raise DefaultMaxExpansionDepth in the source or refactor into a compose-style expansion.
  4. Log/print the Expand chain per step to identify which formula repeats.

Example fix

// before (mutual cycle)
// a.formula.toml: [[template]] expand = "b"
// b.formula.toml: [[template]] expand = "a"
// after
// b.formula.toml: [[template]] id = "real-step" (no back-reference to a)
Defensive patterns

Strategy: validation

Validate before calling

func assertAcyclicExpand(parser *formula.Parser, names []string) error {
	seen := map[string]bool{}
	var walk func(name string) error
	walk = func(name string) error {
		if seen[name] {
			return fmt.Errorf("inline expand cycle at %s", name)
		}
		seen[name] = true
		defer delete(seen, name)
		f, err := parser.LoadByName(name)
		if err != nil {
			return err
		}
		for _, s := range f.Template {
			if s.Expand != "" {
				if err := walk(s.Expand); err != nil {
					return err
				}
			}
		}
		return nil
	}
	for _, n := range names {
		if err := walk(n); err != nil {
			return err
		}
	}
	return nil
}

Prevention

When it happens

Trigger: Calling ApplyInlineExpansions on a steps slice where inline Expand references chain through expansion templates that themselves contain Expand steps, more than DefaultMaxExpansionDepth levels deep — typically because formula A's template expands to formula B, and B back to A.

Common situations: Two .formula.toml files accidentally expand each other; copying an Expand line into a template that references its own formula; building deeply nested modular expansions that exceed the fixed depth budget.

Related errors


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