cross-rs/cross · error
changelog entry " " without header
Error message
changelog entry "{line}" without header What it means
read_changelog parses the changelog line by line. A '- ' entry line is only accepted after a '### <section>' header has established the current ChangelogType; if an entry appears before any header (or after the parser lost track), it bails with this error. It enforces that every entry belongs to a declared section.
Solutions
- Move the '- ' entry lines under the appropriate '### Added'/'### Fixed'/etc. header
- Re-add the missing '### <section>' header above the orphaned entries
- Regenerate the changelog with the xtask tooling instead of hand-editing
Example fix
// before ## [Unreleased] - ReleaseDate - my change // after ## [Unreleased] - ReleaseDate ### Added - my change
Defensive patterns
Strategy: validation
Validate before calling
let mut saw_header = false;
for line in text.lines() {
if line.starts_with("### ") { saw_header = true; }
if line.starts_with("- ") && !saw_header {
panic!("entry before any section header: {line}");
}
} Try / catch
match read_changelog(dir) {
Ok(changes) => changes,
Err(e) => { eprintln!("changelog format error, fix headers before entries: {e}"); std::process::exit(1); }
} Prevention
- Always place '### <section>' headers immediately after '## [Unreleased]' before adding bullets
- Run the changelog validation (validate_changelog) in CI on docs changes
- Avoid manual merge-conflict edits that drop header lines
When it happens
Trigger: CHANGELOG.md has '- some entry' lines at the top of the file or directly after '## [Unreleased]' without a preceding '### Section' header; or an earlier invalid header line aborted kind assignment (note from_header's ? already propagates that).
Common situations: Hand-edited changelog where a contributor appends bullet points above the section headers, merges conflict resolution dropped a '### Fixed' line, or a template copied without its headers.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
AI-assisted analysis of cross-rs/cross@8c1a8aa4b6 (2026-09-13).
Data as JSON: /api/errors/467d77a1bcde4772.
Report an issue: GitHub.
Appendix: source
Thrown at xtask/src/changelog.rs:419
.ok_or(eyre::eyre!("could not find the next release section"))?;
let (section, footer) = rest.split_at(last_index);
// the unreleased should have the format:
// ## [Unreleased] - ReleaseDate
//
// ### Added
//
// - #905 - ...
let mut kind = None;
let mut changes = Changes::default();
for line in section {
let line = line.trim();
if let Some(header) = line.strip_prefix("### ") {
kind = Some(ChangelogType::from_header(header)?);
} else if let Some(entry) = line.strip_prefix("- ") {
match kind {
Some(kind) => changes.push(ChangelogEntry::parse(entry, kind)?),
None => eyre::bail!("changelog entry \"{line}\" without header"),
}
} else if !(line.is_empty() || line == "## [Unreleased] - ReleaseDate") {
eyre::bail!("invalid changelog entry, got \"{line}\"");
}
}
Ok((header.join("\n"), changes, footer.join("\n")))
}
fn delete_changes(root: &Path) -> cross::Result<()> {
// move all files to the denoted version release
for entry in fs::read_dir(root.join(".changes"))? {
let entry = entry?;
let file_type = entry.file_type()?;
let srcpath = entry.path();
let ext = srcpath.extension();
if file_type.is_file() && ext.is_some_and(|v| v == "json") {
fs::remove_file(srcpath)?;View on GitHub (pinned to 8c1a8aa4b6)