rust-lang/rust · error · anyhow::Error

list continuation unexpectedly terminated

Error message

list continuation unexpectedly terminated

What it means

Returned by the release-notes parser when a `+` list-continuation marker is followed by end-of-input — the parser peek()s for the continued block (source block, listing, title, image, etc.) but the iterator is empty, so there is nothing to continue.

Solutions

  1. Open the notes file and remove the trailing `+` or add the block it was meant to continue.
  2. Run the parser on the file standalone to get the line number, then fix that line.
  3. Add a lint/test that rejects a file ending in a continuation marker.

Example fix

<!-- before: notes.md ends with a dangling continuation -->
* Some bullet
+ <!-- nothing follows; EOF -->

<!-- after: either drop the marker or supply the block -->
* Some bullet
+
----
code block content
----
Defensive patterns

Strategy: validation

Validate before calling

// Reject release notes that end in a dangling continuation before running publish:
fn ends_cleanly(text: &str) -> bool {
    let trimmed = text.trim_end();
    !trimmed.ends_with('\n+') && !trimmed.ends_with('+')
}

Type guard

null

Try / catch

match publish::notes::parse(&notes) {
    Ok(out) => Ok(out),
    Err(e) if e.to_string().contains("list continuation") => {
        eprintln!("notes file has a dangling `+`; remove it or add the block it continues");
        Err(e)
    }
    Err(e) => Err(e),
}

Prevention

When it happens

Trigger: Running the publish/notes xtask on a changelog/release-notes Markdown file where a list item ends with a bare `+` on the last line (no following block). The AsciiDoc-style continuation expects content after it.

Common situations: Editing the release notes and leaving a dangling `+` at the end of the file; a merge that truncated the notes; copy-paste that dropped the block following the continuation marker.

Related errors


AI-assisted analysis of rust-lang/rust@7088e4b63a (2026-08-10). Data as JSON: /api/errors/65e018aa3ba2316b. Report an issue: GitHub.

Appendix: source

Thrown at src/tools/rust-analyzer/xtask/src/publish/notes.rs:106

        let mut nesting = ListNesting::default();
        while let Some(line) = self.iter.peek() {
            let line = line.as_deref().map_err(|e| anyhow!("{e}"))?;

            if get_list_item(line).is_some() {
                let line = self.iter.next().unwrap()?;
                let line = process_inline_macros(&line)?;
                let (marker, item) = get_list_item(&line).unwrap();
                nesting.set_current(marker);
                self.write_list_item(item, &nesting);
                self.process_paragraph(nesting.indent(), |line| {
                    line.is_empty() || get_list_item(line).is_some() || line == "+"
                })?;
            } else if line == "+" {
                let _ = self.iter.next().unwrap()?;
                let line = self
                    .iter
                    .peek()
                    .ok_or_else(|| anyhow!("list continuation unexpectedly terminated"))?;
                let line = line.as_deref().map_err(|e| anyhow!("{e}"))?;

                let indent = nesting.indent();
                if line.starts_with('[') {
                    self.write_line("", 0);
                    self.process_source_code_block(indent)?;
                } else if line.starts_with(LISTING_DELIMITER) {
                    self.write_line("", 0);
                    self.process_listing_block(None, indent)?;
                } else if line.starts_with('.') {
                    self.write_line("", 0);
                    self.process_block_with_title(indent)?;
                } else if line.starts_with(IMAGE_BLOCK_PREFIX) {
                    self.write_line("", 0);
                    self.process_image_block(None, indent)?;
                } else if line.starts_with(VIDEO_BLOCK_PREFIX) {
                    self.write_line("", 0);
                    self.process_video_block(None, indent)?;

View on GitHub (pinned to 7088e4b63a)