{"record":{"id":"f536175722e29879","repo":"gastownhall/beads","slug":"expansion-depth-limit-exceeded-max-d-levels-cur","errorCode":null,"errorMessage":"expansion depth limit exceeded: max %d levels (currently at %d) - step %q","messagePattern":"expansion depth limit exceeded: max (.+?) levels \\(currently at (.+?)\\) - step %q","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/formula/expand.go","lineNumber":198,"sourceCode":"\n// countStepIDs counts occurrences of each step ID recursively.\nfunc countStepIDs(steps []*Step, counts map[string]int) {\n\tfor _, step := range steps {\n\t\tcounts[step.ID]++\n\t\tif len(step.Children) > 0 {\n\t\t\tcountStepIDs(step.Children, counts)\n\t\t}\n\t}\n}\n\n// expandStep expands a target step using the given template.\n// Returns the expanded steps with placeholders substituted.\n// The depth parameter tracks recursion depth for children; if it exceeds\n// DefaultMaxExpansionDepth, an error is returned.\n// The vars parameter provides variable values for {varname} substitution.\nfunc expandStep(target *Step, template []*Step, depth int, vars map[string]string) ([]*Step, error) {\n\tif depth > DefaultMaxExpansionDepth {\n\t\treturn nil, fmt.Errorf(\"expansion depth limit exceeded: max %d levels (currently at %d) - step %q\",\n\t\t\tDefaultMaxExpansionDepth, depth, target.ID)\n\t}\n\n\tresult := make([]*Step, 0, len(template))\n\n\tfor _, tmpl := range template {\n\t\texpanded := &Step{\n\t\t\tID:             substituteVars(substituteTargetPlaceholders(tmpl.ID, target), vars),\n\t\t\tTitle:          substituteVars(substituteTargetPlaceholders(tmpl.Title, target), vars),\n\t\t\tDescription:    substituteVars(substituteTargetPlaceholders(tmpl.Description, target), vars),\n\t\t\tType:           tmpl.Type,\n\t\t\tPriority:       tmpl.Priority,\n\t\t\tAssignee:       substituteVars(tmpl.Assignee, vars),\n\t\t\tSourceFormula:  tmpl.SourceFormula,  // Preserve source from template\n\t\t\tSourceLocation: tmpl.SourceLocation, // Preserve source location\n\t\t}\n\n\t\t// Substitute placeholders in labels","sourceCodeStart":180,"sourceCodeEnd":216,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/formula/expand.go#L180-L216","documentation":"expandStep tracks recursion depth; if depth exceeds DefaultMaxExpansionDepth (5), it aborts with this error. This guards against infinite/recursive expansions where templates reference themselves or expansions cascade unboundedly through nested children.","triggerScenarios":"Any expandStep call chain reaching depth 6+: recursive expansion templates, an expansion whose output is re-expanded by a map/inline rule, or chained expansions across formulas (callers include ApplyExpansions, MaterializeExpansion, applyInlineExpansionsRecursive).","commonSituations":"Self-referential formula (template expands to steps that match the same expansion rule); mutually recursive expansions; a bug in select patterns matching newly created steps.","solutions":["Break the recursion in the template so expansion is finite","Make select patterns exclude generated steps (prefix-based patterns)","Restructure deeply nested generations into explicit steps or raise depth intentionally via the library constant if truly needed"],"exampleFix":"// before: template expands steps that re-match the rule\n// after: generate IDs that don't match the select pattern\nsteps:\n  - id: \"done-${i}\"   # select: \"gen-*\" no longer matches","handlingStrategy":"validation","validationCode":"// static check: expansion templates must not reference steps matching their own select/expand rule\nfunc templateSelfReferences(f *formula.Formula, pattern string) bool {\n    for _, s := range f.Template {\n        if matched, _ := path.Match(pattern, s.ID); matched { return true }\n    }\n    return false\n}","typeGuard":null,"tryCatchPattern":"steps, err := formula.ApplyExpansions(steps, compose)\nif err != nil && strings.Contains(err.Error(), \"depth limit exceeded\") {\n    return fmt.Errorf(\"recursive expansion detected (max %d): %w\", 5, err)\n}","preventionTips":["Never write templates whose generated IDs re-match the expansion rule's select pattern","Name generated steps with a prefix excluded from select patterns","Add unit tests (like TestExpandStepDepthLimit) for recursive formula combinations"],"tags":["recursion","expansion","depth-limit"],"backgroundTag":"expansion-depth-limit-exceeded","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}