gastownhall/beads · error

applying expansions to %q: %w

Error message

applying expansions to %q: %w

What it means

Thrown when formula.ApplyExpansions fails applying compose-level expansion operators (compose.expand / compose.map) to the resolved steps. It names the formula being cooked and wraps the operator failure, distinguishing compose-operator failures from inline-expansion failures (error 270).

Source

Thrown at cmd/bd/cook.go:756

	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 to %q: %w", formulaName, 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 to %q: %w", formulaName, 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. Read the wrapped cause to see which expand/map operator failed; fix the referenced step ID or template
  2. Validate the compose block: every expand/map target must match an existing step id
  3. Define all variables used by compose.map templates in the formula's vars section
  4. Validate the formula YAML with the parser before cooking (verifyFormula path)
  5. Re-sync the formula source if the compose block drifted from upstream

Example fix

# before
compose:
  expand:
    - target: buld   # typo, no such step
# after
compose:
  expand:
    - target: build  # matches steps[].id
Defensive patterns

Strategy: validation

Validate before calling

// Validate compose operators reference existing step IDs before cooking
stepIDs := map[string]bool{}
for _, s := range f.Steps { stepIDs[s.ID] = true }
if f.Compose != nil {
    for _, ex := range f.Compose.Expand {
        if !stepIDs[ex.Target] {
            return fmt.Errorf("compose.expand target %q not a step id", ex.Target)
        }
    }
}

Try / catch

if err := runCook(); err != nil && strings.Contains(err.Error(), "applying expansions") {
    var inner error = errors.Unwrap(err)
    log.Printf("compose operator failed: %v", inner) // fix the named expand/map target
}

Prevention

When it happens

Trigger: A formula with a `compose:` block declaring `expand:` or `map:` operators is cooked, and an operator references a nonexistent step ID, an invalid template, or fails variable substitution during expansion. Triggered via bd cook, bd pour, or verifyFormula on that formula.

Common situations: compose.expand references a step id renamed elsewhere in the YAML; compose.map uses a template variable not defined in vars; hand-edited formula YAML left a stale step id in the compose block.

Related errors


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