Nodes/ComPyter/Jupyter Breakpoint
ComfyUI Node

Jupyter Breakpoint

Pause the graph, attach a terminal

By Sxela·Created 4 months ago·Updated 4 months ago· 14
Jupyter Breakpoint
  • value
  • value
pausetrue
label
sessiondefault

ComfyUI has no debugger. You get a paused execution if a node throws, a console full of logs, and otherwise you're guessing what shape that latent is. JupyterBreakpoint is the closest thing to breakpoint() the platform has: drop it on a wire, queue, and the graph stops dead while a real Jupyter kernel hands you the incoming value to poke at. Reassign value, call resume(), and the graph continues with whatever you bound it to.

It's the pro/headless half of ComPyter, sxela's two-node pack (the other node is Jupyter Notebook, which puts the code cell inside the node body). The Breakpoint is the same idea with none of the in-node chrome: you get a copy-pasteable attach block in the console instead of a UI, and you bring your own front-end. Same singleton kernel, same sessions, and you can mix the two nodes freely in one graph - they share the namespace.

How it works

Both nodes live off one persistent IPython kernel that starts lazily on the first hit and survives across queues - no ComfyUI restart. When the Breakpoint executes, it binds value, label, and a resume() function into the kernel namespace, then blocks on a pause event until you call resume(). Meanwhile the console prints something like:

[Jupyter Breakpoint: breakpoint] paused. Connect a front-end:
  jupyter console   --existing /home/me/.local/share/jupyter/runtime/comfyui_jupyter_breakpoint.json

Copy that line into a terminal and you're in a real REPL. Inspect value.shape, value.dtype, plot it, mutate it. Whatever value is bound to when you call resume() is what the next node receives. The attach block is tied to your node's title (right-click → Rename) rather than a hidden label field - the label STRING input still exists but is legacy, kept so old workflows load.

The inputs that matter

The schema is minimal on purpose - this is a probe, not a tool chest:

  • value - required, any type. What gets bound into the kernel namespace.
  • pause - BOOLEAN, default true. Flip it to false and the node becomes a pure, fully-cached passthrough: no pause, no attach block printed. Handy for leaving the node in a workflow without it stopping you.
  • session - STRING, default "default". Partitions kernel state; same value shares variables, different values isolate. The same session controls the sibling Notebook node too.

Output is a single value, same type, passthrough unless you reassigned it in the kernel.

Install

cd ComfyUI/custom_nodes
git clone https://github.com/sxela/ComPyter ComPyter
pip install -r ComPyter/requirements.txt
# restart ComfyUI once

Or search ComPyter in ComfyUI Manager. Dependencies are just ipykernel and jupyter_client (Python 3.10+) - no model files, nothing heavy. You'll want one front-end too: jupyter console ships with the Jupyter install, jupyter qtconsole gets you inline plots, and jupyter_existing_provisioner unlocks a full Lab GUI against the existing kernel.

Common issues

The "jupyter lab won't attach" trap. A plain Jupyter server only talks to kernels it spawned itself, so jupyter lab --existing <file> fails by design. Use jupyter console --existing or qtconsole --existing (both work directly), or install jupyter_existing_provisioner and launch lab with the provisioner env var the attach block shows.

You're on a remote GPU box. The kernel binds 127.0.0.1 only. Copy the connection file down and SSH-tunnel the five ZMQ ports (they're listed in the JSON) before running jupyter console --existing. The README walks through the exact tunnel command.

It "never pauses." Check that pause is actually true - it defaults to on, but if you flipped it for a previous pass you get silent passthrough.

One honest warning before you wire this into anything you share: an attached kernel is arbitrary code execution in the ComfyUI process. It binds localhost only, but don't expose those ZMQ ports publicly - tunnel them. And like any breakpoint, it's a debugging instrument; pull it out of a workflow before you hand it to someone else, because a paused graph looks an awful lot like a broken one.

Categorydebug

Inputs (4)

NameTypeDefaultDescription
value*
pauseBOOLEANtrue
labelSTRING
sessionSTRINGdefault

Outputs (1)

NameTypeDescription
value*