Skip to content

Commit a4c74ba

Browse files
committed
Make notebook health check non-blocking and surface full tracebacks in summary
1 parent d1bef88 commit a4c74ba

1 file changed

Lines changed: 40 additions & 30 deletions

File tree

scripts/check-notebook-health.py

Lines changed: 40 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,16 @@
22
"""
33
Inspect all notebook outputs under static/ and report failures.
44
5-
Behaviour:
5+
Behaviour (always exits 0 — this is a diagnostic step, not a gate):
66
- Writes a Markdown summary table to $GITHUB_STEP_SUMMARY (if set) listing
7-
every failed notebook per package/version with its truncated error.
8-
- Emits ::warning:: log lines so GitHub annotates each failed notebook
9-
visibly on the Actions run page.
10-
- Exits with code 1 iff the *latest* version of any package has at least one
11-
failed notebook output. Latest is determined from the package manifest's
12-
`latestTag`. Historical versions can fail without blocking deployment.
7+
every failed notebook per package/version with the full captured error.
8+
- Emits ::warning:: log lines so GitHub annotates each failed notebook on the
9+
Actions run page (annotation text is truncated for legibility).
10+
11+
Workflow gating: build.py exits non-zero when a build crashes outright;
12+
per-notebook execution failures are deliberately not blocking because a
13+
single broken example shouldn't withhold the other healthy versions from
14+
being deployed.
1315
1416
Usage (called from CI):
1517
python scripts/check-notebook-health.py
@@ -53,7 +55,11 @@ def _strip_ansi(s: str) -> str:
5355

5456

5557
def collect_failures() -> dict[tuple[str, str], list[tuple[str, str]]]:
56-
"""Return {(package, tag): [(notebook_stem, error_message), ...]} for failed outputs."""
58+
"""Return {(package, tag): [(notebook_stem, full_error), ...]} for failed outputs.
59+
60+
The full error string is preserved; annotation formatting decides on its own
61+
how much to surface.
62+
"""
5763
failures: dict[tuple[str, str], list[tuple[str, str]]] = {}
5864

5965
for pkg_dir in sorted(p for p in STATIC_DIR.iterdir() if p.is_dir()):
@@ -70,8 +76,6 @@ def collect_failures() -> dict[tuple[str, str], list[tuple[str, str]]]:
7076
if data is None or data.get("success") is not False:
7177
continue
7278
error = _strip_ansi(str(data.get("error", "unknown error"))).strip()
73-
if len(error) > 200:
74-
error = error[:197] + "..."
7579
failures.setdefault((pkg_dir.name, version_dir.name), []).append(
7680
(output_file.stem, error)
7781
)
@@ -102,37 +106,43 @@ def main() -> int:
102106
Path(summary_path).write_text("### Notebook health\n\n" + msg)
103107
return 0
104108

105-
# GitHub annotations — appear inline on the Actions run page.
109+
# GitHub annotations — single-line, so we strip newlines and trim hard.
106110
for (pkg, tag), items in failures.items():
107111
for stem, err in items:
108-
print(f"::warning title=Notebook failure::{pkg}/{tag}/{stem}: {err}")
109-
110-
# Step summary.
111-
lines = ["### Notebook health\n", f"\n{sum(len(v) for v in failures.values())} failed notebook output(s) across {len(failures)} version(s).\n"]
112-
lines.append("\n| Package | Version | Latest? | Notebook | Error |\n")
113-
lines.append("|---|---|---|---|---|\n")
112+
tail = err.replace("\n", " ⏎ ")
113+
if len(tail) > 240:
114+
tail = tail[-240:] # tail of the traceback is where the real exception sits
115+
print(f"::warning title=Notebook failure::{pkg}/{tag}/{stem}: ...{tail}")
116+
117+
# Step summary with the FULL error in a collapsible <details> per notebook
118+
# so the run page surfaces every traceback in readable form.
119+
n_failed = sum(len(v) for v in failures.values())
120+
lines: list[str] = [
121+
"### Notebook health\n\n",
122+
f"{n_failed} failed notebook output(s) across {len(failures)} version(s).\n\n",
123+
]
114124
for (pkg, tag), items in sorted(failures.items()):
115-
is_latest = "**yes**" if latest.get(pkg) == tag else ""
125+
is_latest = " (latest)" if latest.get(pkg) == tag else ""
126+
lines.append(f"#### `{pkg}/{tag}`{is_latest}\n\n")
116127
for stem, err in items:
117-
# Escape pipe characters for Markdown tables.
118-
err_md = err.replace("|", "\\|").replace("\n", " ")
119-
lines.append(f"| {pkg} | {tag} | {is_latest} | `{stem}` | {err_md} |\n")
128+
lines.append(f"<details><summary><code>{stem}</code></summary>\n\n")
129+
lines.append("```\n")
130+
lines.append(err.rstrip())
131+
lines.append("\n```\n\n</details>\n\n")
120132

133+
output = "".join(lines)
121134
if summary_path:
122-
Path(summary_path).write_text("".join(lines))
135+
Path(summary_path).write_text(output)
123136
else:
124-
sys.stdout.write("".join(lines))
137+
sys.stdout.write(output)
125138

126-
# Hard-fail iff latest version of any package has failures.
127-
blocking = sorted({pkg for (pkg, tag) in failures if latest.get(pkg) == tag})
128-
if blocking:
139+
latest_failing = sorted({pkg for (pkg, tag) in failures if latest.get(pkg) == tag})
140+
if latest_failing:
129141
print(
130-
f"\nBlocking: latest version of {', '.join(blocking)} has failed notebooks.",
142+
f"\nLatest version of {', '.join(latest_failing)} has failed notebooks "
143+
"(diagnostic only — does not block deployment).",
131144
file=sys.stderr,
132145
)
133-
return 1
134-
135-
print("\nFailures only in historical versions — not blocking deployment.")
136146
return 0
137147

138148

0 commit comments

Comments
 (0)