{"record":{"id":"99c32ebf85f7ee0e","repo":"spectreconsole/spectre.console","slug":"encountered-closing-tag-when-none-was-expected-nea","errorCode":null,"errorMessage":"Encountered closing tag when none was expected near position {token.Position}.","messagePattern":"Encountered closing tag when none was expected near position (.+?)\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/Spectre.Console.Ansi/AnsiMarkup.cs","lineNumber":77,"sourceCode":"        var link = default(Link?);\n\n        while (tokenizer.MoveNext())\n        {\n            var token = tokenizer.Current;\n\n            if (token.Kind == MarkupTokenKind.Open)\n            {\n                var parsed = AnsiMarkupTagParser.Parse(token.Value);\n                linkStack.Push(link);\n                link = parsed.Link ?? link;\n                styleStack.Push(style.Value);\n                style = style.Value.Combine(parsed.Style);\n            }\n            else if (token.Kind == MarkupTokenKind.Close)\n            {\n                if (styleStack.Count == 0)\n                {\n                    throw new InvalidOperationException(\n                        $\"Encountered closing tag when none was expected near position {token.Position}.\");\n                }\n\n                style = styleStack.Pop();\n                link = linkStack.Pop();\n            }\n            else if (token.Kind == MarkupTokenKind.Text)\n            {\n                if (result.Count > 0 && result[^1].Style.Equals(style) && Equals(result[^1].Link, link))\n                {\n                    // 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));","sourceCodeStart":59,"sourceCodeEnd":95,"githubUrl":"https://github.com/spectreconsole/spectre.console/blob/0acc92fada6c42f13984e79c2b5f3d993bdfb099/src/Spectre.Console.Ansi/AnsiMarkup.cs#L59-L95","documentation":"In AnsiMarkup.Parse the tokenizer yielded a Close token (a `[/]`) but the style stack is empty, meaning there is no matching opening tag to close. The position reported is the token's position in the source markup.","triggerScenarios":"Markup like `\"text[/]\"`, `\"[/]text\"`, or any string containing more `[/]` closers than `[...]` openers, passed to AnsiMarkup.Parse or AnsiMarkup.Write.","commonSituations":"Concatenating markup fragments and losing an opening tag; copy-pasting only the closing half of a styled span; user input containing literal `[/]`; a templating layer that strips `[tag]` but leaves `[/]`.","solutions":["Ensure every `[/]` has a preceding matching `[...]` opener","Escape literal brackets in dynamic text with AnsiMarkup.Escape(text) before splicing into markup","Sanitize user input: strip or escape stray `[/]` sequences","Use try/catch around AnsiMarkup.Parse when rendering untrusted markup"],"exampleFix":"// before (stray closing tag)\nAnsiMarkup.Parse(\"Hello[/] world\");\n\n// after\nAnsiMarkup.Parse(\"Hello world\");\n// or, for dynamic text, escape it first:\nAnsiMarkup.Parse(AnsiMarkup.Escape(userText));","handlingStrategy":"validation","validationCode":"// Check that openers and closers balance before parsing:\npublic static bool IsMarkupBalanced(string markup)\n{\n    int depth = 0;\n    for (int i = 0; i < markup.Length; i++)\n    {\n        if (markup[i] == '[' && i + 1 < markup.Length && markup[i + 1] != '[') depth++;\n        else if (markup[i] == ']' && i + 1 < markup.Length && markup[i + 1] == ']') i++; // escaped\n        else if (markup[i] == ']' && i > 0 && markup[i - 1] == ']') { /* already handled */ }\n    }\n    return depth == 0;\n}","typeGuard":null,"tryCatchPattern":"try { var segments = AnsiMarkup.Parse(markup); }\ncatch (InvalidOperationException ex) when (ex.Message.StartsWith(\"Encountered closing tag\"))\n{ /* log and fall back to plain text */ }","preventionTips":["Escape all dynamic text with AnsiMarkup.Escape before splicing into markup","Render untrusted strings as plain text rather than markup","Wrap AnsiMarkup.Parse in try/catch when input provenance is uncertain"],"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"}