rust-lang/mdBook · critical

pop too far processing `{}`

Error message

pop too far processing `{}`

What it means

mdbook-html builds a DOM tree from markdown/pulldown-cmark events using a tag_stack; pop() removes the current element and moves to its parent. If pop() is called when only the root remains on the stack, the stack would underflow, so the code panics with this message including the source file path. It means the event stream was unbalanced — an End event arrived with no matching Start.

Source

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

        self.current_node = new_node;
    }

    /// Append a new child to the current node, and make the new node the current node.
    ///
    /// As compared to `push`, it is *not* expected that there will be a `pop` called
    /// for this node. The next call to `pop` will unwind the stack past this node.
    fn push_no_stack(&mut self, node: Node) {
        let new_node = self.append(node);
        self.current_node = new_node;
    }

    /// Switch the current node to the current node's parent.
    fn pop(&mut self) {
        self.tag_stack.pop();
        if let Some(&parent) = self.tag_stack.last() {
            self.current_node = parent;
        } else {
            panic!("pop too far processing `{}`", self.options.path.display());
        }
    }

    /// Returns all of the [`NodeId`]s, filtering out just the [`Element`]
    /// nodes where the given callback returns `true` based on the element
    /// name.
    fn node_ids_for_tag(&self, filter: &dyn Fn(&str) -> bool) -> Vec<NodeId> {
        self.tree
            .nodes()
            .filter(|node| {
                let Node::Element(el) = node.value() else {
                    return false;
                };
                filter(el.name())
            })
            .map(|node| node.id())
            .collect()
    }

View on GitHub (pinned to dc21064fc2)

Solutions

  1. Find and fix the unbalanced HTML/markdown in the file named in the panic message (options.path).
  2. Run any custom preprocessors' output through an HTML validator to ensure tags are balanced.
  3. Check pulldown-cmark version compatibility: an event-consumption change in the parser can leave streams unbalanced; pin/upgrade the parser version consistently.
  4. If caused by a preprocessor, fix it to emit a Start for every End tag it emits.

Example fix

// before (src/chapter.md)
some text
</div>

// after (src/chapter.md)
<div>
some text
</div>
Defensive patterns

Strategy: validation

Validate before calling

// preprocessor-side: verify balanced tag emission before passing events on
let mut depth = 0i32;
for ev in &events {
    match ev {
        Event::Start(_) => depth += 1,
        Event::End(_) => { depth -= 1; assert!(depth >= 0, "unbalanced End event"); }
        _ => {}
    }
}

Try / catch

std::panic::catch_unwind(|| render_book(&book)).unwrap_or_else(|_| {
    eprintln!("render panicked: inspect the file in the panic path for unbalanced HTML tags");
    std::process::exit(1);
});

Prevention

When it happens

Trigger: An unbalanced markdown event stream while processing events: an end_tag/start_tag/footnote_reference sequence that pops more times than it pushed, e.g. stray closing tags in inline HTML passed through to the renderer, or a footnote reference processed outside of any open element.

Common situations: Markdown containing malformed inline HTML with unmatched closing tags (e.g. `</div>` with no opener), plugins/preprocessors injecting unbalanced HTML into chapter content, or a bug in a custom pulldown-cmark event filter that drops Start events but keeps End events.

Related errors


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