{"record":{"id":"99168c9201e7e99d","repo":"emberjs/ember.js","slug":"closing-tag-tag-name-did-not-match-last-open","errorCode":null,"errorMessage":"Closing tag </${tag.name}> did not match last open tag <${element.tag}> (on line ${element.loc.startPosition.line})","messagePattern":"Closing tag </(.+?)> did not match last open tag <(.+?)> \\(on line (.+?)\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/@glimmer/syntax/lib/parser/tokenizer-event-handlers.ts","lineNumber":594,"sourceCode":"  }\n\n  validateEndTag(\n    tag: StartTag | EndTag,\n    element: ASTv1.ParentNode,\n    selfClosing: boolean\n  ): asserts element is ASTv1.ElementNode {\n    if (voidMap.has(tag.name) && !selfClosing) {\n      // EngTag is also called by StartTag for void and self-closing tags (i.e.\n      // <input> or <br />, so we need to check for that here. Otherwise, we would\n      // throw an error for those cases.\n      throw generateSyntaxError(\n        `<${tag.name}> elements do not need end tags. You should remove it`,\n        tag.loc\n      );\n    } else if (element.type !== 'ElementNode') {\n      throw generateSyntaxError(`Closing tag </${tag.name}> without an open tag`, tag.loc);\n    } else if (element.tag !== tag.name) {\n      throw generateSyntaxError(\n        `Closing tag </${tag.name}> did not match last open tag <${element.tag}> (on line ${element.loc.startPosition.line})`,\n        tag.loc\n      );\n    }\n  }\n\n  assembleAttributeValue(\n    parts: ASTv1.AttrPart[],\n    isQuoted: boolean,\n    isDynamic: boolean,\n    span: src.SourceSpan\n  ): ASTv1.AttrValue {\n    if (isDynamic) {\n      if (isQuoted) {\n        return this.assembleConcatenatedValue(parts);\n      } else {\n        assertPresentArray(parts);\n","sourceCodeStart":576,"sourceCodeEnd":612,"githubUrl":"https://github.com/emberjs/ember.js/blob/26f97246a8bf2e28edf26ac3093da2e86c04ffc5/packages/@glimmer/syntax/lib/parser/tokenizer-event-handlers.ts#L576-L612","documentation":"validateEndTag compares the closing tag name to the tag of the innermost open element. If they differ, the error reports both names and the line where the open tag started, pinpointing the actual mismatch.","triggerScenarios":"`</span>` closing while `<div>` is the last open element (nesting crossed), e.g. `<div><span></div></span>`.","commonSituations":"Incorrectly nested inline/block elements, JSX-to-HBS conversions, hand-edits that reordered close tags, editor auto-complete closing the wrong element.","solutions":["Close tags in the reverse order they were opened, or reorder the nesting","Match the closing tag name to the last open tag (see line number in the error)","Reformat the template so element nesting is visually obvious"],"exampleFix":"// before\n<div><span></div></span>\n// after\n<div><span></span></div>","handlingStrategy":"try-catch","validationCode":"// stack-based nesting check\nconst stack = [];\nfor (const m of template.matchAll(/<(\\/)?(\\w+)/g)) {\n  if (m[1]) { if (stack.pop() !== m[2]) throw new Error(`mismatched </${m[2]}>`); }\n  else stack.push(m[2]);\n}","typeGuard":null,"tryCatchPattern":"try { parse(template) } catch (e) { if (/did not match last open tag/.test(e.message)) { /* jump to reported open-tag line and fix nesting */ } else throw e; }","preventionTips":["Close elements in LIFO order","Indent nested elements consistently","Let the editor auto-generate closing tags"],"tags":["glimmer","handlebars","html","tag-mismatch"],"backgroundTag":"tag-mismatch","analyzedSha":"26f97246a8bf2e28edf26ac3093da2e86c04ffc5","analyzedAt":"2026-09-01T05:01:28.182Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}