fix(notebooks): drop cascade errors from snapshot parsing
All checks were successful
CI / lint (push) Successful in 31s
CI / notebooks-smoke (push) Successful in 1m28s
Deploy / notebooks (push) Has been skipped
Deploy / zotero (push) Has been skipped
Deploy / docs (push) Has been skipped
Deploy / api (push) Has been skipped
Deploy / mc (push) Has been skipped
Deploy / report (push) Successful in 13s
CI / test (push) Successful in 13m32s

marimo marks every descendant of a failed or stopped cell with its own
error output ('An ancestor raised an exception', 'ancestor-stopped').
The root cause is always present as a non-ancestor error in the same
snapshot, so cascades only inflate the report — and each one filed its
own deduplicated issue (6 of the 12 nb issues from the first nightly
run were cascade noise). Also makes intentional mo.stop() flow control
count as a pass instead of a failure.
This commit is contained in:
kert
2026-07-10 11:38:30 -04:00
parent 15ab55a286
commit cc094bd1bb
2 changed files with 47 additions and 0 deletions

View File

@@ -82,6 +82,14 @@ def parse_snapshot(path: Path) -> list[dict]:
for out in cell.get("outputs", []): for out in cell.get("outputs", []):
if out.get("type") != "error": if out.get("type") != "error":
continue continue
# Cascade noise: marimo marks every descendant of a failed or
# stopped cell with its own error output. The root cause is
# always present as a non-ancestor error in the same snapshot,
# so these only inflate the report (and the issue tracker).
if out.get("ename") == "ancestor-stopped" or out.get(
"evalue", ""
).startswith("An ancestor raised an exception"):
continue
detail = "" detail = ""
for con in cell.get("console", []): for con in cell.get("console", []):
if con.get("name") == "stderr": if con.get("name") == "stderr":

View File

@@ -86,6 +86,45 @@ def test_parse_snapshot_extracts_errors_with_console_detail(tmp_path):
assert "intentional failure" in e["detail"] assert "intentional failure" in e["detail"]
def test_parse_snapshot_drops_cascade_errors(tmp_path):
"""Descendants of a failed/stopped cell get their own error outputs;
only the root-cause error should survive parsing."""
snap = {
"version": "1",
"metadata": {"marimo_version": "0.23.13"},
"cells": [
SNAPSHOT_ERR["cells"][1],
{
"id": "ecfG",
"outputs": [
{
"type": "error",
"ename": "exception",
"evalue": "An ancestor raised an exception (ValueError): ",
"traceback": None,
}
],
"console": [],
},
{
"id": "xXTn",
"outputs": [
{
"type": "error",
"ename": "ancestor-stopped",
"evalue": "This cell wasn't run because an ancestor "
"was stopped with `mo.stop`: ",
"traceback": None,
}
],
"console": [],
},
],
}
errors = nbi.parse_snapshot(_write(tmp_path, "cascade.py.json", snap))
assert [e["cell"] for e in errors] == ["MJUe"]
def test_parse_snapshot_missing_file_reports_export_error(tmp_path): def test_parse_snapshot_missing_file_reports_export_error(tmp_path):
errors = nbi.parse_snapshot(tmp_path / "never-written.py.json") errors = nbi.parse_snapshot(tmp_path / "never-written.py.json")
assert len(errors) == 1 assert len(errors) == 1