astral-sh/ruff · error

Failed to read notebook file `{}`: {:?}

Error message

Failed to read notebook file `{}`: {:?}

What it means

`ruff_notebook::round_trip` runs Jupyter notebook round-trip source generation on a file path. If the notebook cannot be loaded from disk (missing file, parse error, schema failure), the underlying Notebook::from_path error is wrapped with the path and error detail into this anyhow error.

Source

Thrown at crates/ruff_notebook/src/notebook.rs:25

use std::io;
use std::io::{BufReader, Cursor, Read, Seek, SeekFrom, Write};
use std::path::Path;
use std::sync::OnceLock;
use thiserror::Error;

use ruff_diagnostics::{SourceMap, SourceMarker};
use ruff_source_file::{OneIndexed, UniversalNewlineIterator};
use ruff_text_size::{TextRange, TextSize};

use crate::cell::CellOffsets;
use crate::index::NotebookIndex;
use crate::schema::{Cell, RawNotebook, SortAlphabetically, SourceValue};
use crate::{CellMetadata, CellStart, RawNotebookMetadata, SYNTHETIC_CELL_SEPARATOR, schema};

/// Run round-trip source code generation on a given Jupyter notebook file path.
pub fn round_trip(path: &Path) -> anyhow::Result<String> {
    let mut notebook = Notebook::from_path(path).map_err(|err| {
        anyhow::anyhow!(
            "Failed to read notebook file `{}`: {:?}",
            path.display(),
            err
        )
    })?;
    let code = notebook.source_code().to_string();
    let needs_rebuild = notebook.update_cell_content(&code);
    debug_assert!(
        !needs_rebuild,
        "round-tripping unchanged source cannot remove a synthetic cell separator"
    );
    let mut writer = Vec::new();
    notebook.write(&mut writer)?;
    Ok(String::from_utf8(writer)?)
}

/// An error that can occur while deserializing a Jupyter Notebook.
#[derive(Error, Debug)]

View on GitHub (pinned to 26f38c119c)

Solutions

  1. Verify the path exists and is readable (`ls -l`, check permissions)
  2. Validate the notebook JSON (e.g. `jq . notebook.ipynb` or jupyter's nbformat validate) and fix or regenerate the file
  3. Re-run after the notebook is saved/closed to rule out a concurrent-writer partial file
  4. If the file is intentionally not a notebook, remove it from the lint/format target list

Example fix

// before
let code = round_trip(Path::new("outdated_notebook.ipynb"))?;
// after
let path = Path::new("analysis.ipynb");
assert!(path.exists(), "notebook missing: {}", path.display());
let code = round_trip(path)?;
Defensive patterns

Strategy: try-catch

Validate before calling

// Rust-side pre-checks before round_trip
use std::path::Path;

fn notebook_readable(path: &Path) -> bool {
    path.is_file()
        && std::fs::File::open(path)
            .map(|mut f| {
                use std::io::Read;
                let mut buf = String::new();
                f.read_to_string(&mut buf).is_ok()
                    && serde_json::from_str::<serde_json::Value>(&buf).is_ok()
            })
            .unwrap_or(false)
}

Try / catch

// Rust
match ruff_notebook::round_trip(path) {
    Ok(code) => process(code),
    Err(e) if e.to_string().contains("Failed to read notebook file") => {
        eprintln!("skipping notebook {path:?}: {e:#}");
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling `ruff_notebook::round_trip(path)` where `path` does not exist, is not readable, or contains a malformed/invalid `.ipynb` JSON document.

Common situations: Typo'd or stale notebook path passed to `ruff check --fix` on notebooks; notebooks produced by tools emitting non-conforming JSON; files deleted or moved between discovery and processing; permission problems in CI containers.

Related errors


AI-assisted analysis of astral-sh/ruff@26f38c119c (2026-09-05). Data as JSON: /api/errors/43c91e9330a63679. Report an issue: GitHub.