Skip to content

CI blind spot: interactive ipywidgets callbacks aren't exercised (slider-state bugs slip through) #441

@kwlee2025cpp

Description

@kwlee2025cpp

Problem

CI runs notebooks via jupyter nbconvert --execute — each cell once, top-to-bottom, with no widget interaction. So bugs that only appear when a user interacts with an ipywidgets control (moves a slider), or that depend on shared global state mutated by later cells, are invisible to CI.

Concrete example (just fixed in 60_linear_algebra_2/200)

The Power Method arrow-slider callback show_iterate(step) read the global n. Cells 49/52/58 (lam, vecX, n = power_method(...)) overwrite n99999 for the non-converging "What if" matrices. After running those cells, moving the slider raised:

ValueError: shape mismatch ... (99999,) vs (2,)   # ax.bar(range(99999), vec[2])

CI passed cleanly because:

  • the interact callback only fires once, at creation, when n was still correct;
  • nbconvert never moves the slider, and never re-fires the callback after n is clobbered;
  • the if os.getenv('CI'): show_iterate(last) guard renders one static frame — by design, to keep CI green — which also means the interactive path is never exercised.

(Fixed by capturing the dimension locally: dim = vec_array.shape[1], independent of the mutable global n.)

Options

  1. Convention (cheapest): widget callbacks must capture all needed state locally in their own cell, never reading mutable globals that later cells reassign. Make it a review/lint checklist item.
  2. Exercise callbacks in a test: for notebooks containing interact(...), after executing all cells, call the callback across its full slider range and assert no exception. Non-trivial to wire generically.
  3. Static check: flag a widget callback that references a module-level name reassigned elsewhere in the same notebook. Hard to do reliably (needs data-flow analysis).

Related

Same family as #440 and the "missing Colab clone cell" gap — CI executes the repo, non-interactive path, not the interactive/Colab path.

🤖 Filed via Claude Code after fixing the 200 slider crash.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions