Jupyter Notebooks skill for Claude Code

Restart, run all, same answer every time. Claude restructures notebooks into a fixed layout with one tagged parameters cell, removes absolute paths and in-notebook installs, pins the environment behind a named kernel, and moves reusable logic into modules with tests. It sets up stripped outputs or jupytext pairing for clean diffs and proves reproducibility with a headless run from a fresh kernel.

  • namejupyter-notebooks
  • size188 lines · ≈1,850 tokens
  • files1
  • licenseMIT
  • updated

What Claude sees before loading it

This description is the only part of the skill that sits in context all the time. Claude reads it to decide when to load the full playbook below.

description: Makes Jupyter notebooks reproducible and reviewable — a standard cell structure, restart-and-run-all execution with nbconvert or papermill, a tagged parameters cell, pinned kernels and environments, stripped outputs or jupytext pairing for clean git diffs, nbqa linting, and extraction of reusable code into tested modules. Use when creating or cleaning up .ipynb files, when a notebook only works if cells run in a certain order, when notebooks must run on a schedule with different parameters, or when notebook diffs are unreadable in pull requests.

Try asking

  • This notebook only works if you run the cells in a weird order. Make it reproducible
  • Turn this analysis notebook into a monthly report we can run with different dates
  • Our notebook diffs are unreadable in PRs. Set up something better

Use it when

  • A notebook depends on hidden state or manual steps
  • The same notebook must run with different parameters or on a schedule
  • Notebook changes are hard to review in git
  • Notebook code should become reusable, tested functions

Skip it when

  • The notebook has become a production ETL job — use data-pipelines instead
  • The question is about the analysis itself rather than the notebook — use pandas-analysis instead

How to check the result

A headless nbconvert or papermill run from a fresh kernel finishes without errors, execution counts are sequential, key totals match the previous run, and re-running produces an empty git diff.

The common mistake it prevents

Sharing a notebook that was never restarted and run top to bottom, so its saved outputs depend on cells that were deleted or run out of order.

~/.claude/skills/jupyter-notebooks/SKILL.md

--- name: jupyter-notebooks description: Makes Jupyter notebooks reproducible and reviewable — a standard cell structure, restart-and-run-all execution with nbconvert or papermill,… license: MIT ---

A notebook is reproducible when a fresh kernel can run it top to bottom with no manual steps, on a pinned environment, with inputs set in one parameters cell, producing the same outputs; it is reviewable when its diff in git shows code changes, not megabytes of JSON output.

Ground rules

  • Restart-and-run-all before sharing or committing. A notebook that has not been executed linearly is not done.
  • One parameters cell near the top holds every input (dates, paths, thresholds), tagged parameters. No hardcoded values deeper in the notebook.
  • Paths relative to the project root; no /Users/me/..., no files on someone’s desktop.
  • Functions used in more than one notebook, or longer than ~30 lines, move to a module with tests.
  • Never commit secrets: read credentials from environment variables, and check outputs for printed tokens before committing.
  • Seed every source of randomness.

Workflow

- [ ] 1. Inspect: kernel, environment, execution order, hidden state, data paths
- [ ] 2. Restructure into the standard layout
- [ ] 3. Pin the environment and register a named kernel
- [ ] 4. Parameterize; remove hardcoded values
- [ ] 5. Extract reusable code to a module with autoreload
- [ ] 6. Set up git hygiene (nbstripout or jupytext) and lint
- [ ] 7. Execute headless from a clean kernel; compare results

1. Inspect

python - <<'EOF'
import json, sys
nb = json.load(open(sys.argv[1] if len(sys.argv) > 1 else "analysis.ipynb"))
counts = [c.get("execution_count") for c in nb["cells"] if c["cell_type"] == "code"]
print("kernel:", nb["metadata"].get("kernelspec", {}).get("name"))
print("execution counts:", counts)
print("in order:", [c for c in counts if c] == sorted(c for c in counts if c))
EOF

Out-of-order or missing execution counts mean hidden state: the saved outputs may not be reproducible. Also grep for absolute paths, !pip install, and input() calls.

2. Standard layout

CellContents
1 (markdown)Title, question answered, owner, data sources, last run date
2 (code, tag parameters)All inputs: dates, paths, thresholds, sample sizes
3Imports, settings (pd.set_option, plotting style), seeds
4Load data (via module functions) with shape printed
5..nSections with a markdown header each: clean -> analyze -> visualize
Last (markdown)Findings, caveats, next steps; numbers referenced from computed variables
# parameters
start_date = "2026-09-01"
end_date = "2026-09-30"
input_path = "data/raw/orders.parquet"
output_dir = "out"
min_orders = 5
seed = 42
from pathlib import Path
import numpy as np
import pandas as pd

rng = np.random.default_rng(seed)
PROJECT_ROOT = Path.cwd().resolve()
while not (PROJECT_ROOT / "pyproject.toml").exists() and PROJECT_ROOT != PROJECT_ROOT.parent:
    PROJECT_ROOT = PROJECT_ROOT.parent
Path(PROJECT_ROOT / output_dir).mkdir(exist_ok=True)

3. Environment and kernel

uv add --dev ipykernel                      # or: pip install ipykernel into the project venv
uv run python -m ipykernel install --user --name myproject --display-name "Python (myproject)"
jupyter kernelspec list

Pin dependencies in pyproject.toml + lockfile (or requirements.txt with versions). Replace !pip install cells with the project’s dependency file; installing inside a notebook makes environments drift silently.

4. Parameterize and run headless

# Execute in place from a fresh kernel (fails on the first exception)
jupyter nbconvert --to notebook --execute --inplace analysis.ipynb

# Parameterized runs with papermill (cell tagged "parameters" gets overridden)
papermill analysis.ipynb out/analysis_2026-10.ipynb \
  -p start_date 2026-10-01 -p end_date 2026-10-31 -k myproject

# Render a shareable report without code
jupyter nbconvert --to html --no-input out/analysis_2026-10.ipynb

Add the parameters tag via the cell’s property inspector in JupyterLab, or in the cell metadata: "tags": ["parameters"].

5. Extract code to modules

%load_ext autoreload
%autoreload 2
from myproject.orders import load_orders, monthly_revenue

Move into src/myproject/ anything that is reused, longer than a screen, or has logic worth testing (cleaning rules, metric definitions). Keep in the notebook: parameters, calls, exploratory views, plots, narrative. Write a pytest test for each extracted function with a small fixture DataFrame.

6. Git hygiene

Pick one approach per repo:

ApproachSetupTradeoff
Strip outputs on commitnbstripout --install (adds a git filter)Clean diffs; outputs not in git (re-run to see them)
Pair with a text filejupytext --set-formats ipynb,py:percent analysis.ipynb; commit the .py, optionally ignore .ipynbReviewable code; edit either side, jupytext --sync
Keep outputs (reports)Commit executed notebook deliberately; use nbdime (nbdiff, git nbdiffdriver)Large diffs; good for published results

Lint and format notebooks like code:

nbqa ruff analysis.ipynb
nbqa ruff analysis.ipynb --fix

7. Verify

  1. Restart kernel and run all (or the nbconvert --execute command) completes without errors on a clean checkout.
  2. Execution counts are 1..n in order.
  3. Key outputs (final table totals, metric values) match the previous run or the difference is explained by new data.
  4. git diff after a re-run with no code changes is empty (with nbstripout/jupytext).

Review checklist

  • Parameters cell tagged and complete
  • No absolute paths, no !pip install, no credentials in code or outputs
  • Seeds set; results stable across two runs
  • Each section has a markdown header explaining what and why
  • Findings cell numbers come from variables, not typed by hand
  • Heavy/reused logic lives in modules with tests
  • Output size reasonable (no 50 MB embedded plots or full DataFrame dumps)

Gotchas

  • Deleting a cell does not delete the variables it created; only a restart reveals the dependency.
  • df.head() outputs of sensitive data end up in git history; strip outputs or sample synthetic rows.
  • Relative paths depend on the kernel’s working directory (often the notebook’s folder, but not under papermill with --cwd or in some IDEs); resolve from the project root.
  • Interactive widgets and Plotly figures may not render in nbconvert HTML without the right renderer settings; check the exported report.
  • Long-running notebooks on schedules should be modules or pipeline tasks; papermill is fine for parameterized reports, not for production ETL.
  • Marimo notebooks (.py files with reactive execution) avoid hidden state by design; consider them for new interactive work if the team agrees.

Output

**Notebook:** notebooks/revenue_analysis.ipynb (+ paired revenue_analysis.py via jupytext)
**Kernel:** Python (myproject), deps locked in uv.lock
**Parameters:** start_date, end_date, input_path, output_dir, min_orders, seed
**Extracted:** load_orders(), monthly_revenue() -> src/myproject/orders.py (4 tests)
**Headless run:** nbconvert --execute OK in 48 s; counts 1..23 in order; totals unchanged
**Git:** nbstripout installed; diff after re-run is empty

About this skill

Written and maintained by the Claude Skills editors to the same editorial standard as every skill in the directory: a selective trigger, a real workflow, known pitfalls and a verification step. First published , last updated . MIT licensed.

esc