{"record":{"id":"2ade3f89b880a354","repo":"emberjs/ember.js","slug":"invalid-end-tag-closing-tag-must-not-be-self-clos","errorCode":null,"errorMessage":"Invalid end tag: closing tag must not be self-closing","messagePattern":"Invalid end tag: closing tag must not be self-closing","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/@glimmer/syntax/lib/parser/tokenizer-event-handlers.ts","lineNumber":199,"sourceCode":"      element.closeTag = null;\n    } else if (element.selfClosing) {\n      assert(element.closeTag === null, 'element.closeTag unexpectedly present');\n    } else {\n      element.closeTag = closeTagStart.until(this.offset());\n    }\n\n    element.loc = element.loc.withEnd(this.offset());\n\n    appendChild(parent, b.element(element));\n  }\n\n  markTagAsSelfClosing(): void {\n    let tag = this.currentTag;\n\n    if (tag.type === 'StartTag') {\n      tag.selfClosing = true;\n    } else {\n      throw generateSyntaxError(\n        `Invalid end tag: closing tag must not be self-closing`,\n        this.source.spanFor({ start: tag.start.toJSON(), end: this.offset().toJSON() })\n      );\n    }\n  }\n\n  // Tags - name\n\n  appendToTagName(char: string): void {\n    let tag = this.currentTag;\n    tag.name += char;\n\n    if (tag.type === 'StartTag') {\n      let offset = this.offset();\n\n      if (tag.nameStart === null) {\n        assert(tag.nameEnd === null, 'nameStart and nameEnd must both be null');\n","sourceCodeStart":181,"sourceCodeEnd":217,"githubUrl":"https://github.com/emberjs/ember.js/blob/26f97246a8bf2e28edf26ac3093da2e86c04ffc5/packages/@glimmer/syntax/lib/parser/tokenizer-event-handlers.ts#L181-L217","documentation":"Self-closing syntax (/>) is only valid on start (opening) tags. When markTagAsSelfClosing runs and the current tag is an EndTag — e.g. </div/> — the tokenizer cannot mark a closing tag as self-closing, so it throws a syntax error spanning the tag.","triggerScenarios":"Writing a closing tag with a trailing slash: </div/>, </ul />, </:slot/>. The parser invokes markTagAsSelfClosing when it encounters '/' inside the tag; since tag.type is 'EndTag' the else branch throws.","commonSituations":"Typos where the self-closing slash meant for a start tag (<br/>) drifted onto the end tag; hand-editing templates and leaving </div/>; codemods or string concatenation producing malformed close tags.","solutions":["Remove the trailing slash from the closing tag: </div/> → </div>.","If the element was meant to be self-closing, put the slash on the start tag instead and delete the separate end tag: <img src=\"x.png\" /> with no </img>.","Re-check the surrounding lines for duplicated '/' characters left over from an edit."],"exampleFix":"// before\n<div class=\"box\">\n  content\n</div/>\n\n// after\n<div class=\"box\">\n  content\n</div>","handlingStrategy":"validation","validationCode":"// reject self-closing end tags before compiling\nfunction assertNoSelfClosingEndTags(template) {\n  const bad = template.match(/<\\s*\\/\\s*[A-Za-z][^>]*\\/\\s*>/g);\n  if (bad) throw new Error(`Closing tag must not be self-closing: ${bad.join(', ')}`);\n}\nassertNoSelfClosingEndTags(template);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Put the '/' only on start tags (<img />), never on closing tags","Grep templates for '</' followed by '/' as a quick check","When generating templates programmatically, emit closing tags without trailing slashes","Format templates with a formatter (prettier-plugin-ember-template-tag etc.) that normalizes tags"],"tags":["glimmer","handlebars","template-syntax","html-parsing","end-tag"],"backgroundTag":"glimmer-invalid-end-tag","analyzedSha":"26f97246a8bf2e28edf26ac3093da2e86c04ffc5","analyzedAt":"2026-09-01T05:01:28.182Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}