Two owner-reported bugs, both verified as gaps that never closed (the old
code on master had them too).
Cancel: the node never polled ComfyUI's interrupt flag, so the Cancel
button had no effect. A new src/keep_interrupt.py wraps
comfy.model_management.throw_exception_if_processing_interrupted() (lazy
import, a no-op outside ComfyUI); it is called at every long-loop
boundary - detection, crop, restore chunks, paste frames, and the paste
module's group/chunk loops. InterruptProcessingException is a
BaseException, so it escapes the pipeline untouched.
Multi-face: the pipeline collapsed to one face per frame - the detector
call passed only_keep_largest=True (the vendored selector reduces to the
largest face) and the processor then took all_landmarks_5[0]. With
only_center_face=False the new branches keep every detected face:
detection returns all faces, the crops/restore/paste flatten to the
ragged per-frame face count, the device canvas composites several ROIs
per row, and the vendored fallback hands the helper all of a frame's
faces. The near-black tail follows the real vendored reference (one
convert per image after a forward face loop). only_center_face=True and
the aligned path keep their bodies byte-for-byte.
44 new pins: cancel (the exception escapes the sequence and the node, no
work after it, unchanged output without a cancel), multi-face image and
video (all faces restored and pasted, faces appearing/disappearing,
None-affine and black-face drops, per-row near-black tail against the
vendored composite), and the paste core (face-to-frame map, row
compositing, chunk boundaries that never split a frame's faces).
Verified on the CPU device only - no model loads, no GPU runs. Gate: 505
passed, 6 skipped, 7 deselected, 0 failed (461 baseline + 44 new pins).
The pack owned a private stdlib handler, so every line bypassed the coloured console of ComfyUI, and the call sites carried a literal KEEP prefix. The logger now adopts the host console (no handler and propagate=True when ComfyUI is present, one private handler when it is not), the tag lives in a single filter, and the console writes of the pack (the tqdm bars of the processor, the prints of the vendored facelib) reach it through a thread-scoped redirect.