can1357/oh-my-pi · error

Parse error: unclosed comment

Error message

Parse error: unclosed comment

What it means

parseTemplate() scans the template for Handlebars-style `{{...}}` expressions. A `{{!-- ... --}}` comment open tag must be terminated by `--}}`; if the parser finds no closing delimiter anywhere after the opener it throws "Parse error: unclosed comment". This is a hard parse failure — the whole template fails to compile.

Source

Thrown at packages/utils/src/template.ts:241

	}
	throw new Error("Parse error: unclosed template expression");
}

function parseTemplate(source: string): Node[] {
	const root: Node[] = [];
	const stack: { node: BlockNode; target: Node[]; inverted: boolean }[] = [];
	let target = root;
	let cursor = 0;
	while (cursor < source.length) {
		const open = source.indexOf("{{", cursor);
		if (open < 0) {
			if (cursor < source.length) target.push({ kind: "text", value: source.slice(cursor) });
			break;
		}
		if (open > cursor) target.push({ kind: "text", value: source.slice(cursor, open) });
		if (source.startsWith("{{!--", open)) {
			const end = source.indexOf("--}}", open + 6);
			if (end < 0) throw new Error("Parse error: unclosed comment");
			cursor = end + 4;
			continue;
		}
		const triple = source.startsWith("{{{", open);
		const contentStart = open + (triple ? 3 : 2);
		const end = findTagEnd(source, contentStart, triple);
		const raw = source.slice(contentStart, end).trim();
		cursor = end + (triple ? 3 : 2);
		if (!raw || raw.startsWith("!")) continue;
		if (raw === "else" || raw.startsWith("else ")) {
			const current = stack[stack.length - 1];
			if (!current) throw new Error("Parse error: unexpected else");
			target = current.node.inverse;
			if (raw.length > 4) {
				const parentTarget = target;
				const nested: BlockNode = {
					kind: "block",
					expression: parseCall(raw.slice(5)),

View on GitHub (pinned to 9690622007)

Solutions

  1. Locate every `{{!--` in the template source and ensure each has a matching `--}}` before the end of the file.
  2. If the comment was meant to be prose, escape the braces (e.g. `{{'{{!--'}}` or plain text) so the parser does not see an open tag.
  3. If the source comes from a file or env var, log/inspect the full template string to spot truncation at read time.

Example fix

// before
{{!-- notes about the section
<div>...</div>
// after
{{!-- notes about the section --}}
<div>...</div>
Defensive patterns

Strategy: try-catch

Validate before calling

function hasBalancedHandlebarsComments(src) {
  let i = 0;
  while ((i = src.indexOf('{{!--', i)) !== -1) {
    const end = src.indexOf('--}}', i + 6);
    if (end === -1) return false;
    i = end + 4;
  }
  return true;
}
if (!hasBalancedHandlebarsComments(templateSrc)) throw new Error('template has an unclosed {{!-- comment');

Try / catch

try {
  render(templateSrc, data);
} catch (err) {
  if (err instanceof Error && err.message.startsWith('Parse error:')) {
    // surface a template-authoring problem, not a runtime data problem
    throw new Error(`Invalid template: ${err.message}`);
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling any template render/compile entry (parseTemplate is reached from public parse/render APIs) with source containing `{{!--` but no matching `--}}` later in the string. Note the search starts at open+6, so an immediately adjacent `--}}` is not treated as the closer.

Common situations: Truncating templates when saving/generating them (a file cut off mid-comment); copy-pasting Handlebars docs where the closing delimiter was dropped; escaping problems where a literal `{{!--` appears in generated docs or license text; a templating migration that left a commented-out block unterminated.

Understand the failure class

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/f7d3682ece9cca08. Report an issue: GitHub.