astral-sh/ruff · error
Expected a Title
Error message
Expected a Title
What it means
ruff_annotate_snippets is the vendored annotate-snippets renderer used for ruff/ty diagnostics. When a Renderer is built with .short_message(true), Renderer::render() delegates to render_short_message(), which emits rustc-style one-line messages ('path:line:col: level[id]: title'). That path requires the first group to carry a title (set via Level::primary_title()/secondary_title()); a report built without one violates the invariant and panics with 'Expected a Title'.
Source
Thrown at crates/ruff_annotate_snippets/src/renderer/render.rs:244
}
buffer
.render(&level, &renderer.stylesheet, &mut out_string)
.unwrap();
if g != group_len - 1 {
out_string.push('\n');
}
}
out_string
}
}
fn render_short_message(renderer: &Renderer, groups: &[Group<'_>]) -> Result<String, fmt::Error> {
let mut buffer = StyledBuffer::new();
let mut labels = None;
let group = groups.first().expect("Expected at least one group");
let Some(title) = &group.title else {
panic!("Expected a Title");
};
if let Some(Element::Cause(cause)) = group
.elements
.iter()
.find(|e| matches!(e, Element::Cause(_)))
{
let labels_inner = cause
.markers
.iter()
.filter_map(|ann| match &ann.label {
Some(msg) if ann.kind.is_primary() => {
if !msg.trim().is_empty() {
Some(msg.to_string())
} else {
None
}
}View on GitHub (pinned to d1087a4b9e)
Solutions
- Attach a title to the Level before adding elements: Level::ERROR.primary_title("...") (or secondary_title).
- If no title exists, render with the normal (non-short) renderer instead of short_message(true).
- Add a unit test that renders the diagnostic with a short_message(true) renderer so the invariant is enforced in CI.
Example fix
// before: no title on the Level -> panic: Expected a Title
let input = &[Level::ERROR
.element(Snippet::source(src)
.annotation(AnnotationKind::Primary.span(0..5)))];
let out = Renderer::plain().short_message(true).render(input);
// after
let input = &[Level::ERROR
.primary_title("E501 line too long")
.element(Snippet::source(src)
.annotation(AnnotationKind::Primary.span(0..5)))];
let out = Renderer::plain().short_message(true).render(input); Defensive patterns
Strategy: validation
Validate before calling
// Lock the invariant with a test that renders every diagnostic in short mode.
#[test]
fn diagnostics_render_short_message() {
for report in all_diagnostic_reports() { // each report must carry a title
let _ = Renderer::plain().short_message(true).render(report);
}
} Try / catch
let rendered = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
renderer.render(&report)
}))
.unwrap_or_else(|_| report.title_only_fallback()); Prevention
- Treat a title as required whenever the renderer runs in short_message mode; set it in the same builder chain as the first element.
- Cover every diagnostic with a concise/short render test so a missing title fails CI, not users.
- When making titles optional, switch affected paths to the full renderer explicitly.
When it happens
Trigger: Calling Renderer::plain().short_message(true).render(...) with a Level whose group has no title — e.g. a new diagnostic wired into concise/short rendering that sets only .element(Snippet...) annotations and never .primary_title(...).
Common situations: Adding a new diagnostic and forgetting the title; refactoring titles to be optional in shared rendering code; writing snapshot tests for the short format without a title.
Related errors
- unexpected annotation type: {annotation:?}
- Annotation range `{bigger:?}` is beyond the end of buffer `{
- Patch span `{bigger:?}` is beyond the end of buffer `{source
- Expected a ty `File`, found a ruff `SourceFile`
- should be set because `extract_if` only yields elements with
AI-assisted analysis of astral-sh/ruff@d1087a4b9e (2026-08-20).
Data as JSON: /api/errors/f5e142987e09bf18.
Report an issue: GitHub.