Version control, diffs and reproducible output

Why notebook JSON produces unreadable commits, how to strip outputs automatically, and how to make a notebook that renders identically on another machine.

Why notebooks diff badly

An .ipynb is JSON containing source, execution counts, outputs, metadata and base64 images. Changing one number in a plot regenerates a large blob, so a one-line edit can produce a thousand-line diff.

  • Execution counters like [17] change even when the code does not.
  • Outputs embed PNG data, which no review tool can read usefully.
  • Cell ids and metadata order vary between Jupyter versions.
  • Two branches touching the same cell almost always conflict.
⚠️
Notebook outputs are a data leak. A traceback may print a token, a connection string or customer rows, and those land in the repository permanently. Assume anything ever shown in an output cell is public to everyone with repository access.

Stripping outputs automatically

pip install nbstripout
nbstripout --install               # configures the git filter for this repository

# check it is wired up
git config --get filter.nbstripout.clean

# strip a single file manually
nbstripout analysis.ipynb
# .gitattributes
*.ipynb filter=nbstripout
*.ipynb diff=jupyternotebook
*.ipynb merge=jupyternotebook

The clean filter runs on the way into the index, so outputs are removed from what you commit while your working file keeps them. The diff and merge drivers give you a cell-aware view instead of raw JSON.

Deterministic output

import random, numpy as np

SEED = 42
random.seed(SEED)
np.random.seed(SEED)

# set display options that affect output but not results
np.set_printoptions(precision=4, suppress=True)
Source of differenceFix
Library versionsPin with pip freeze, a lockfile, or an environment file
RandomnessSeed every generator, including in library calls you pass a seed to
Floating point orderingAvoid relying on last-digit equality across machines
Timestamps in outputDo not print now() inside a report cell
Locale-dependent formattingFormat numbers explicitly rather than relying on defaults

Combine the strip filter with pinned dependencies and the notebook becomes reviewable code plus a reproducible document.

FAQ

Can I keep outputs for the final report and strip them elsewhere?
Yes. Strip by default through the git filter, and publish the executed HTML with nbconvert --execute as a build artefact instead of committing outputs into the notebook.
What if a teammate already committed a notebook with a secret in the output?
Stripping future commits does not remove history. Treat the credential as compromised, rotate it immediately, and only then decide whether a history rewrite is worth the disruption.

Notebooks vs scripts: jupytext, nbconvert and papermill A notebook workflow that scales

Last refreshed 2026-09-18.