{"record":{"id":"34544b6e9681e4a4","repo":"gastownhall/beads","slug":"materializing-expansion-q-w","errorCode":null,"errorMessage":"materializing expansion %q: %w","messagePattern":"materializing expansion %q: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/formula/expand.go","lineNumber":433,"sourceCode":"// This enables expansion formulas to be directly instantiated via wisp/pour\n// without requiring a Compose wrapper (bd-qzb).\n//\n// No-op if the formula is not an expansion type, has no Template, or already\n// has Steps.\nfunc MaterializeExpansion(f *Formula, targetID string, vars map[string]string) error {\n\tif f.Type != TypeExpansion || len(f.Template) == 0 || len(f.Steps) > 0 {\n\t\treturn nil\n\t}\n\n\ttarget := &Step{\n\t\tID:          targetID,\n\t\tTitle:       f.Formula,\n\t\tDescription: f.Description,\n\t}\n\n\texpandedSteps, err := expandStep(target, f.Template, 0, vars)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"materializing expansion %q: %w\", f.Formula, err)\n\t}\n\n\tf.Steps = expandedSteps\n\treturn nil\n}\n\n// ApplyInlineExpansions applies Step.Expand fields to inline expansions.\n// Steps with the Expand field set are replaced by the referenced expansion template.\n// The step's ExpandVars are passed as variable overrides to the expansion.\n//\n// This differs from compose.Expand in that the expansion is declared inline on the\n// step itself rather than in a central compose section.\n//\n// Returns a new steps slice with inline expansions applied.\n// The original steps slice is not modified.\nfunc ApplyInlineExpansions(steps []*Step, parser *Parser) ([]*Step, error) {\n\tif parser == nil {\n\t\treturn steps, nil","sourceCodeStart":415,"sourceCodeEnd":451,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/formula/expand.go#L415-L451","documentation":"MaterializeExpansion builds concrete steps for an expansion formula by calling expandStep with depth 0 and the merged vars. If expandStep fails (depth limit or substitution error), the error is wrapped with the formula name for context.","triggerScenarios":"Calling MaterializeExpansion on a formula whose template causes expandStep to fail — e.g. template placeholders referencing variables absent from f.Vars, or (via inline expansion) recursion pushing depth past 5.","commonSituations":"Materializing a formula with missing variable defaults; templates that inline-expand into themselves; partially edited templates with broken {var} references.","solutions":["Check the wrapped inner error for the root cause","Define all template variables as defaults in the expansion formula or pass them via vars","Remove recursive inline expansions from the template"],"exampleFix":"# before: uses ${env} but env undefined\nsteps:\n  - id: \"run-${env}\"\n# after\nvars:\n  env: dev\nsteps:\n  - id: \"run-${env}\"","handlingStrategy":"try-catch","validationCode":"for _, v := range requiredVars(f.Template) { // scan {var} placeholders\n    if _, ok := vars[v]; !ok { return fmt.Errorf(\"missing var %q for formula %q\", v, f.Formula) }\n}","typeGuard":"func hasAllVars(f *formula.Formula, vars map[string]string) bool {\n    for _, name := range templateVarNames(f.Template) {\n        if _, ok := vars[name]; !ok { return false }\n    }\n    return true\n}","tryCatchPattern":"if err := formula.MaterializeExpansion(f); err != nil {\n    if strings.Contains(err.Error(), \"depth limit\") {\n        return fmt.Errorf(\"formula %s expansion too deep: %w\", f.Formula, err)\n    }\n    return err\n}","preventionTips":["Define defaults for every variable a template uses","Avoid inline self-expansion inside templates","Unit-test MaterializeExpansion for each expansion formula"],"tags":["expansion","materialize","variables"],"backgroundTag":"expansion-variable-substitution-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}