{"record":{"id":"95eed0ea1f7983b5","repo":"spectreconsole/spectre.console","slug":"unbalanced-markup-stack-did-you-forget-to-close-a","errorCode":null,"errorMessage":"Unbalanced markup stack. Did you forget to close a tag?","messagePattern":"Unbalanced markup stack\\. Did you forget to close a tag\\?","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/Spectre.Console.Ansi/AnsiMarkup.cs","lineNumber":106,"sourceCode":"                    // Merge segments with same style and link\n                    result[^1].Text += token.Value;\n                }\n                else\n                {\n                    result.Add(\n                        new AnsiMarkupSegment(\n                        token.Value, style.Value, link));\n                }\n            }\n            else\n            {\n                throw new InvalidOperationException(\"Encountered unknown markup token.\");\n            }\n        }\n\n        if (styleStack.Count > 0)\n        {\n            throw new InvalidOperationException(\"Unbalanced markup stack. Did you forget to close a tag?\");\n        }\n\n        // Try resolving auto links (if any)\n        ResolveAutoLinks(result);\n\n        return result;\n    }\n\n    private static void ResolveAutoLinks(List<AnsiMarkupSegment> segments)\n    {\n        for (var segmentIndex = 0; segmentIndex < segments.Count; segmentIndex++)\n        {\n            var currentLink = segments[segmentIndex].Link;\n            if (currentLink?.Url.Equals(Constants.EmptyLink, StringComparison.Ordinal) != true)\n            {\n                // Not a link\n                continue;\n            }","sourceCodeStart":88,"sourceCodeEnd":124,"githubUrl":"https://github.com/spectreconsole/spectre.console/blob/0acc92fada6c42f13984e79c2b5f3d993bdfb099/src/Spectre.Console.Ansi/AnsiMarkup.cs#L88-L124","documentation":"After consuming all tokens in AnsiMarkup.Parse, the style stack is non-empty, meaning one or more opening tags `[...]` were never closed with a matching `[/]`. The error prompts the developer to add the missing closer.","triggerScenarios":"Markup such as `\"[red]text\"`, `\"[bold][blue]hi\"`, or any input where openers outnumber closers, passed to AnsiMarkup.Parse/Write.","commonSituations":"Forgotten `[/]` at the end of a styled segment; nested tags where only the inner one is closed (`[red][bold]x[/]`); templating that drops trailing closers; truncation of a markup string.","solutions":["Add a matching `[/]` for every `[...]` opener","For nested styles, close in the correct order or rely on the stack (each `[/]` pops one level)","Validate tag balance before rendering, or escape dynamic text with AnsiMarkup.Escape","Wrap rendering of dynamic markup in try/catch"],"exampleFix":"// before\nAnsiMarkup.Parse(\"[red]Error occurred\");\n\n// after\nAnsiMarkup.Parse(\"[red]Error occurred[/]\");","handlingStrategy":"validation","validationCode":"// Verify every opener has a matching closer:\npublic static bool HasMatchingClosers(string markup)\n{\n    int open = 0;\n    for (int i = 0; i < markup.Length; i++)\n    {\n        char c = markup[i];\n        if (c == '[' && i + 1 < markup.Length && markup[i + 1] == '[') { i++; continue; }\n        if (c == '[') open++;\n        else if (c == ']' && i > 0 && markup[i - 1] == ']') continue;\n        else if (markup.Length - i >= 3 && markup.Substring(i, 3) == \"[/]\") open--;\n    }\n    return open == 0;\n}","typeGuard":null,"tryCatchPattern":"try { var segments = AnsiMarkup.Parse(markup); }\ncatch (InvalidOperationException ex) when (ex.Message.StartsWith(\"Unbalanced markup stack\"))\n{ /* append missing closers or render as plain text */ }","preventionTips":["Always close tags you open, especially in templates","Escape dynamic content with AnsiMarkup.Escape","Use try/catch around rendering of assembled markup"],"tags":["markup","parsing","ansi","spectre-console"],"backgroundTag":null,"analyzedSha":"0acc92fada6c42f13984e79c2b5f3d993bdfb099","analyzedAt":"2026-08-13T18:27:21.983Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}