rust-lang/mdBook · critical

internal error: expected empty tag stack. path: `{}` eleme

Error message

internal error: expected empty tag stack.

 path: `{}`
element={el:?}

What it means

At the end of processing a document, mdbook-html's finish_stack drains its tag_stack. Leftover Element nodes indicate unclosed HTML tags. If the code cannot safely auto-close them (the branch where it warns about unclosed tags is not taken, e.g. the element type doesn't qualify), it panics that the tag stack should be empty. The panic includes the source path and the leftover element for debugging.

Source

Thrown at crates/mdbook-html/src/html/tree.rs:820

        }
        output
    }

    /// Deals with any unclosed elements on the stack.
    fn finish_stack(&mut self) {
        while let Some(node_id) = self.tag_stack.pop() {
            let node = self.tree.get(node_id).unwrap().value();
            match node {
                Node::Fragment => {}
                Node::Element(el) => {
                    if el.was_raw {
                        warn!(
                            "unclosed HTML tag `<{}>` found in `{}`",
                            el.name.local,
                            self.options.path.display()
                        );
                    } else {
                        panic!(
                            "internal error: expected empty tag stack.\n
                             path: `{}`\n\
                             element={el:?}",
                            self.options.path.display()
                        );
                    }
                }
                node => {
                    panic!(
                        "internal error: expected empty tag stack.\n
                         path: `{}`\n\
                         node={node:?}",
                        self.options.path.display()
                    );
                }
            }
        }
    }

View on GitHub (pinned to dc21064fc2)

Solutions

  1. Open the file shown in the panic (path field) and close every HTML tag you open, in correct order.
  2. Remove stray opening tags that were never intended (e.g. leftover `<div>` from an edit).
  3. Validate chapter HTML with a linter (e.g. htmlhint/tidy) as a preprocessor or CI step.
  4. If a preprocessor generates the HTML, fix it to emit balanced Start/End tag events.

Example fix

// before (src/chapter.md)
<div class="callout">
Content...

// after (src/chapter.md)
<div class="callout">
Content...
</div>
Defensive patterns

Strategy: validation

Validate before calling

// CI check: every opened HTML tag in chapter markdown must be closed
let mut open: Vec<String> = vec![];
for tag in html_tags_in_file(path)? {
    match tag {
        HtmlTag::Open(n) => open.push(n),
        HtmlTag::Close(n) => { if open.pop().as_deref() != Some(&n) { bail!("mismatched tag </{}> in {}", n, path.display()); } }
    }
}
if !open.is_empty() { bail!("unclosed tags {:?} in {}", open, path.display()); }

Try / catch

match std::panic::catch_unwind(|| finish_and_render(&mut tree)) {
    Ok(v) => v,
    Err(_) => { eprintln!("unclosed HTML tag: close all tags in the file named in the panic message"); std::process::exit(1); }
}

Prevention

When it happens

Trigger: A chapter's event stream ends (process_events calls finish_stack) while an element pushed by start_tag was never popped by a matching end_tag — i.e. unclosed or mismatched HTML tags in markdown source that fall into the non-warned branch.

Common situations: Markdown with an opening tag like `<table>` or a custom element that is never closed, tags closed in the wrong nesting order, preprocessors appending HTML without closing tags, or truncated files ending mid-HTML.

Related errors


AI-assisted analysis of rust-lang/mdBook@dc21064fc2 (2026-09-01). Data as JSON: /api/errors/80452462d79443a9. Report an issue: GitHub.