can1357/oh-my-pi · error
Parse error: mismatched ${raw}
Error message
Parse error: mismatched ${raw} What it means
When parseTemplate() sees a closing tag `{{/name}}`, it pops the open-block stack and requires that the top block has the same name. If the stack is empty (a close with no open block) or the names differ, it throws `Parse error: mismatched /name`. This catches mis-nested or mis-named block closers at parse time.
Source
Thrown at packages/utils/src/template.ts:287
}
if (raw.startsWith("#") || raw.startsWith("^")) {
const inverted = raw.startsWith("^");
const node: BlockNode = {
kind: "block",
expression: parseCall(raw.slice(1)),
body: [],
inverse: [],
inverted,
};
target.push(node);
stack.push({ node, target, inverted });
target = node.body;
continue;
}
if (raw.startsWith("/")) {
const current = stack.pop();
if (!current || current.node.expression.name !== raw.slice(1).trim())
throw new Error(`Parse error: mismatched ${raw}`);
if (current.inverted) [current.node.body, current.node.inverse] = [current.node.inverse, current.node.body];
target = current.target;
continue;
}
if (raw.startsWith(">")) target.push({ kind: "partial", expression: parseCall(raw.slice(1)) });
else
target.push({
kind: "output",
expression: parseCall(raw.startsWith("&") ? raw.slice(1) : raw),
escaped: !triple && !raw.startsWith("&"),
});
}
if (stack.length) throw new Error(`Parse error: unclosed block ${stack[stack.length - 1].node.expression.name}`);
return root;
}
function pathParts(path: string): string[] {
const parts: string[] = [];View on GitHub (pinned to 9690622007)
Solutions
- Make each closing tag's name match its opening block (`{{#if x}}...{{/if}}`, `{{#each list}}...{{/each}}`).
- Verify nesting order — inner blocks must be closed before outer blocks.
- Remove any closer whose block opener was deleted.
Example fix
// before
{{#each items}}
{{#if active}}{{name}}
{{/each}}
{{/if}}
// after
{{#each items}}
{{#if active}}{{name}}{{/if}}
{{/each}} Defensive patterns
Strategy: validation
Validate before calling
function validateBlockNames(src) {
const stack = [];
for (const t of src.match(/\{\{[#/^][^}]*\}\}/g) ?? []) {
const raw = t.slice(2, -2).trim();
if (raw.startsWith('#') || raw.startsWith('^')) stack.push(raw.slice(1).split(/[\s(]/)[0]);
else if (raw.startsWith('/')) {
const name = raw.slice(1).trim();
if (stack.pop() !== name) return { ok: false, tag: raw };
}
}
return { ok: stack.length === 0, tag: stack[0] };
} Try / catch
try {
render(src, data);
} catch (err) {
const m = /^Parse error: mismatched (.+)$/.exec(err?.message ?? '');
if (m) throw new Error(`Block closer ${m[1]} does not match the open block — fix nesting/naming`);
throw err;
} Prevention
- Copy-paste blocks including their open and close tags together.
- Rename open/close tags atomically with editor multi-cursor or find-replace.
- Run templates through a parse check in unit tests.
When it happens
Trigger: Rendering a template where `{{/each}}` closes an `{{#if}}` block, `{{/if}}` appears with no open block, or closers are nested in the wrong order (e.g. `{{#each}}{{#if}}{{/each}}{{/if}}`).
Common situations: Hand-editing templates and swapping sections around; copy-pasting a block but forgetting to change its closer name; IDE auto-inserting `{{/if}}` after an `{{#each}}` edit.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Parse error: unclosed comment
- Parse error: unexpected else
- Parse error: unclosed block ${stack[stack.length - 1].node.e
- Missing helper: "${call.name}"
- The partial ${node.expression.name} could not be found
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/65f2cbf1b5a73787.
Report an issue: GitHub.