gastownhall/beads · error

standalone expansion %q: %w

Error message

standalone expansion %q: %w

What it means

Thrown when formula.MaterializeExpansion fails to materialize a standalone expansion formula (bd-qzb). Standalone expansions keep content in Template rather than Steps; before cooking, the template is materialized into cookable steps using expansionVars (formula vars merged with conditionVars). Failure here aborts the cook.

Source

Thrown at cmd/bd/cook.go:815

	// Handle standalone expansion formulas (bd-qzb).
	// Expansion formulas store content in Template, not Steps. Materialize
	// the template into Steps using a synthetic "main" target so the normal
	// cooking pipeline can process them.
	if resolved.Type == formula.TypeExpansion && len(resolved.Template) > 0 {
		expansionVars := make(map[string]string)
		for name, def := range resolved.Vars {
			if def != nil && def.Default != nil {
				expansionVars[name] = *def.Default
			}
		}
		if conditionVars != nil {
			for k, v := range conditionVars {
				expansionVars[k] = v
			}
		}
		if err := formula.MaterializeExpansion(resolved, "main", expansionVars); err != nil {
			return nil, fmt.Errorf("standalone expansion %q: %w", formulaName, err)
		}
	}

	// Cook to in-memory subgraph, including variable definitions for default handling
	return cookFormulaToSubgraphWithVars(resolved, resolved.Formula, resolved.Vars)
}

// cookFormulaToSubgraphWithVars creates an in-memory subgraph with variable info attached
func cookFormulaToSubgraphWithVars(f *formula.Formula, protoID string, vars map[string]*formula.VarDef) (*TemplateSubgraph, error) {
	subgraph, err := cookFormulaToSubgraph(f, protoID)
	if err != nil {
		return nil, err
	}
	// Attach variable definitions to the subgraph for default handling during pour
	// Convert from *VarDef to VarDef for simpler handling
	if vars != nil {
		subgraph.VarDefs = make(map[string]formula.VarDef)
		for k, v := range vars {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Read the wrapped cause; fix the template placeholder or supply the missing variable
  2. Pass all required variables at invocation (bd pour --var key=value) or set defaults in the formula's vars section
  3. Validate the expansion template syntax (balanced {{ }} placeholders)
  4. Confirm the formula's type is expansion and its Template field is non-empty
  5. Test materialization via verifyFormula before running the real cook/pour

Example fix

# before
bd pour my-expansion   # template needs $TARGET, not provided
# after
bd pour my-expansion --var TARGET=svc-api
Defensive patterns

Strategy: validation

Validate before calling

// Before pour/cook, ensure all template placeholders have values
required := expansionTemplateVars(f.Template) // extract {{ var }} names
for _, v := range required {
    if _, ok := expansionVars[v]; !ok {
        return fmt.Errorf("expansion requires variable %q", v)
    }
}

Try / catch

if err := runPour(); err != nil && strings.Contains(err.Error(), "standalone expansion") {
    var missing = extractFromCause(err) // e.g. missing var name
    log.Printf("expansion materialization failed: %v — pass --var %s=...", err, missing)
}

Prevention

When it happens

Trigger: Cooking a formula of type expansion: MaterializeExpansion errors because the template is invalid/empty, variable substitution fails (missing required var, malformed placeholder), or the target ID wiring cannot be produced from the template.

Common situations: Invocation omits a variable the expansion template requires (bd pour with insufficient --var flags); expansion template edited with a bad placeholder like {{varName without closing braces}; expansion formula's template references another formula unavailable at materialize time.

Related errors


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