gastownhall/beads · error

applying expansions: %w

Error message

applying expansions: %w

What it means

cmd/bd/cook.go:196 wraps errors from formula.ApplyExpansions(resolved.Steps, resolved.Compose, parser), which processes compose-level expansion operators (compose.expand and compose.map) to fan steps out over collections or substitute them. It only runs when Compose declares expand/map entries; failure means one of those operators is malformed or references something unresolvable (bad expression, missing target formula, invalid map key).

Source

Thrown at cmd/bd/cook.go:196

	resolved.Steps = controlFlowSteps

	// Apply advice transformations
	if len(resolved.Advice) > 0 {
		resolved.Steps = formula.ApplyAdvice(resolved.Steps, resolved.Advice)
	}

	// Apply inline step expansions
	inlineExpandedSteps, err := formula.ApplyInlineExpansions(resolved.Steps, parser)
	if err != nil {
		return nil, fmt.Errorf("applying inline expansions: %w", err)
	}
	resolved.Steps = inlineExpandedSteps

	// Apply expansion operators
	if resolved.Compose != nil && (len(resolved.Compose.Expand) > 0 || len(resolved.Compose.Map) > 0) {
		expandedSteps, err := formula.ApplyExpansions(resolved.Steps, resolved.Compose, parser)
		if err != nil {
			return nil, fmt.Errorf("applying expansions: %w", err)
		}
		resolved.Steps = expandedSteps
	}

	// Apply aspects from compose.aspects
	if resolved.Compose != nil && len(resolved.Compose.Aspects) > 0 {
		for _, aspectName := range resolved.Compose.Aspects {
			aspectFormula, err := parser.LoadByName(aspectName)
			if err != nil {
				return nil, fmt.Errorf("loading aspect %q: %w", aspectName, err)
			}
			if aspectFormula.Type != formula.TypeAspect {
				return nil, fmt.Errorf("%q is not an aspect formula (type=%s)", aspectName, aspectFormula.Type)
			}
			if len(aspectFormula.Advice) > 0 {
				resolved.Steps = formula.ApplyAdvice(resolved.Steps, aspectFormula.Advice)
			}
		}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Inspect compose.expand/compose.map in the formula and compare each entry's expression and targets against the wrapped error message.
  2. Verify every expand target formula exists in the registry and parses standalone.
  3. Confirm map keys match parameters actually declared/used by the expanded steps.
  4. Simplify to a single expand/map entry and re-run to isolate the failing operator.

Example fix

// before (compose block)
expand:
  - formula: matrix-run   # never defined
// after
expand:
  - formula: run-per-env   # exists in .beads/formulas/
Defensive patterns

Strategy: validation

Validate before calling

func expansionsResolvable(c *formula.ComposeRules, load func(string) (*formula.Formula, error)) error {
	if c == nil {
		return nil
	}
	for _, e := range c.Expand {
		if _, err := load(e.Formula); err != nil {
			return fmt.Errorf("expand target %q unloadable: %w", e.Formula, err)
		}
	}
	for _, m := range c.Map {
		if m.Formula != "" {
			if _, err := load(m.Formula); err != nil {
				return fmt.Errorf("map target %q unloadable: %w", m.Formula, err)
			}
		}
	}
	return nil
}

Type guard

func hasValidExpansions(c *formula.ComposeRules, exists func(string) bool) bool {
	if c == nil {
		return true
	}
	for _, e := range c.Expand {
		if !exists(e.Formula) {
			return false
		}
	}
	return true
}

Try / catch

expanded, err := formula.ApplyExpansions(resolved.Steps, resolved.Compose, parser)
if err != nil {
	return fmt.Errorf("compose expand/map is invalid: %w", err)
}

Prevention

When it happens

Trigger: A formula with compose.expand or compose.map entries where the expansion expression doesn't evaluate, the expanded target formula can't be loaded/parsed, or a map entry references a nonexistent parameter or step.

Common situations: Defining compose.map with a variable the target formula doesn't accept; typo'd formula name in compose.expand; generating permutations from an empty/misused collection expression; upgrading bd and hitting stricter expansion validation than when the formula was written.

Related errors


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