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.
- name
jupyter-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.
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
| Cell | Contents |
|---|---|
| 1 (markdown) | Title, question answered, owner, data sources, last run date |
2 (code, tag parameters) | All inputs: dates, paths, thresholds, sample sizes |
| 3 | Imports, settings (pd.set_option, plotting style), seeds |
| 4 | Load data (via module functions) with shape printed |
| 5..n | Sections 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:
| Approach | Setup | Tradeoff |
|---|---|---|
| Strip outputs on commit | nbstripout --install (adds a git filter) | Clean diffs; outputs not in git (re-run to see them) |
| Pair with a text file | jupytext --set-formats ipynb,py:percent analysis.ipynb; commit the .py, optionally ignore .ipynb | Reviewable 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
- Restart kernel and run all (or the
nbconvert --executecommand) completes without errors on a clean checkout. - Execution counts are 1..n in order.
- Key outputs (final table totals, metric values) match the previous run or the difference is explained by new data.
git diffafter 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
--cwdor 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 (
.pyfiles 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.