kajan988 bc8cb2007c Add kill-all keybind command with toast, silent kill route, bump to v0.0.6
Register a native ComfyUI command (assignable in Settings > Keybindings, no default combo) that kills all comfy-env isolation workers, shows a green success toast, and logs the count. Rewrite /workerkeeper/kill_all to use the silent _shutdown_worker path so no comfy-env shutdown noise appears. Add WEB_DIRECTORY and js/killall.js frontend extension with a WorkerKeeper settings group.
2026-07-20 09:56:45 +03:00
2026-06-14 06:53:14 +03:00
2026-06-14 10:37:05 +03:00

ComfyUI-WorkerKeeper

One optional node. Two hooks. Clean RAM & VRAM.

WorkerKeeper is a background service for ComfyUI that automatically kills idle "comfy-env" (GeometryPack, GaussianPack, SAM3, MoGe2, Sharp, PanoPack, DepthAnythingV3, etc.) isolation subprocesses. It requires zero workflow changes — install and forget. An optional manual override node is also provided.

The Problem

Custom node packs using comfy-env by PozzettiAndrea (GeometryPack, GaussianPack, SAM3, MoGe2, Sharp, PanoPack, DepthAnythingV3, etc.) by spawn persistent Python subprocesses (~2 GB each) for dependency isolation. These subprocesses:

  • Live forever once created — ComfyUI never kills them
  • Occupy 2+ GB of system RAM each
  • Reserve ~200 MB–2 GB of GPU VRAM (model weights loaded in the subprocess)
  • Multiply: 7 packs → 14 GB RAM + VRAM wasted when unused

Before WorkerKeeper: switching from a GeometryPack workflow to a standard image-to-image workflow leaves all 7 subprocesses alive, burning 14 GB for nothing.

How It Works — Two-Layer Architecture

WorkerKeeper uses two independent detection layers, neither of which modifies any ComfyUI core file or any comfy-env environment file:

Layer 1 — on_prompt_handler (Fast Path)

Triggered synchronously before prompt execution begins. Scans the prompt JSON for node class_type values. If an isolation environment has zero nodes in the prompt, its worker is killed immediately.

Cost: ~0.05 ms when no kill is needed; ~50 ms per killed worker.

Layer 2 — ExecutionList.stage_node_execution (Accurate Path)

A zero-invasiveness monkey-patch on ExecutionList.stage_node_execution (one method replacement — no ComfyUI internals are modified, no files are patched on disk). Runs after ComfyUI's cache resolution, so self.pendingNodes contains only the nodes that will actually execute. Kills workers whose environment appears nowhere in the real execution plan.

Cost: ~0.05 ms when no kill is needed; ~50 ms per killed worker.

What We Do NOT Touch

Component Modified?
ComfyUI_CORE/execution.py ❌ No
ComfyUI_CORE/server.py ❌ No
comfy_execution/graph.py ❌ No (runtime class patching only)
Pixi environments (comfy-env/envs/*) ❌ No
comfy-env package ❌ No
Any node's __init__.py ❌ No
Any workflow JSON ❌ No

The proxy class env_dir mapping is extracted via Python closure introspection (reading func.__code__.co_freevars + __closure__) — no files read, no configs parsed.

Scenario Behavior Table

Scenario What Happens Workers Killed Workers Kept
Full workflow with isolation nodes (e.g., GeomPackRemesh → PreviewMesh) Layer 1 sees GeomPack* in prompt. Layer 2 confirms they're in pendingNodes. None geometrypack
Full workflow without isolation nodes (e.g., KSampler → VAEDecode → SaveImage) Layer 1 sees zero isolation class_types. Fast path kills everything. sharp, moge2, geometrypack, gaussianpack, sam3, panopack, depthanythingv3 None
Partial execution on non-isolation nodes (run only KSampler in a mixed workflow) Layer 1 sees all class_types (full prompt). Layer 2 checks pendingNodes — finds only ComfyUI native nodes. All 7 envs None
Partial execution on isolation subgraph (run only GeomPackRemesh) Layer 1 sees GeomPack* in prompt. Layer 2 confirms they're in pendingNodes. sharp, moge2, sam3, etc. geometrypack
All isolation nodes muted or bypassed Layer 1 sees class_types. Layer 2 sees them NOT in pendingNodes (ComfyUI removes bypassed nodes from the execution graph). All 7 envs None
One env muted, one active (GeomPackRemesh active, SharpPredict muted) Layer 1 sees both. Layer 2 sees only GeomPackRemesh in pendingNodes. sharp geometrypack
All nodes cached (re-run identical prompt) Layer 1 sees class_types. Layer 2 finds pendingNodes empty (everything cached). All envs without cached-only nodes in the prompt are killed. Varies —
Workflow switch (GeometryPack → Sharp → standard → GeometryPack) Each prompt re-evaluates. Workers killed/created on demand. See rows above —

Visual Flow

User clicks "Queue Prompt"
          │
          ▼
┌─────────────────────────────────────┐
│  Layer 1: on_prompt_handler         │  ◄── registered via server.PromptServer.instance
│                                     │       .add_on_prompt_handler()
│  Scan prompt JSON for all           │
│  class_type values                  │
│                                     │
│  Any isolation env has 0 nodes      │
│  in the full prompt?                │
│         │ YES                       │
│         ├──► Kill that env's worker │
│         │    (fast: ~50ms)          │
│         └──► Continue               │
└─────────────────────────────────────┘
          │
          ▼
┌─────────────────────────────────────┐
│  ComfyUI resolves caches:           │
│  builds ExecutionList with          │
│  add_node() for each output         │
│                                     │
│  pendingNodes now contains ONLY     │
│  nodes that MUST execute            │
│  (cached nodes are excluded)        │
└─────────────────────────────────────┘
          │
          ▼
┌─────────────────────────────────────┐
│  Layer 2: stage_node_execution      │  ◄── monkey-patch on
│                                     │       ExecutionList (runtime only)
│  (first call only)                  │
│                                     │
│  Scan pendingNodes for isolation    │
│  class_types                        │
│                                     │
│  Any live worker's env has 0        │
│  matches in pendingNodes?           │
│         │ YES                       │
│         ├──► Kill that env's worker │
│         │    (~50ms, silent kill)   │
│         └──► Continue               │
└─────────────────────────────────────┘
          │
          ▼
     Execution begins ───► Nodes run normally, workers created on demand

What "Silent Kill" Means

When WorkerKeeper terminates an idle subprocess, it uses proc.kill() + proc.wait() directly instead of the comfy-env graceful shutdown sequence. This avoids the "RuntimeError: Worker process died" / "Subprocess worker die" messages that appear in ComfyUI logs when the socket-based shutdown handshake is interrupted. The process is terminated instantly, and its temp directory is cleaned up immediately.

No log noise. No tracebacks.

Manual API Endpoints

Two HTTP routes are registered on the ComfyUI server:

Endpoint Method Description
/workerkeeper/status GET Returns JSON with all live workers and their state
/workerkeeper/kill_all POST Kills all isolation workers immediately

Example:

curl http://localhost:8188/workerkeeper/status
# → {"workers": {"C:\\...geometrypack-nodes": {"alive": true, ...}}, "count": 1}

curl -X POST http://localhost:8188/workerkeeper/kill_all
# → {"killed": 1}

Manual Kill Node (Optional)

In addition to the automated background service, WorkerKeeper provides a workflow node for manual control:

Worker Keeper — Manual Kill

Category Value
Class WorkerKeeperManualKill
Category WorkerKeeper
Input trigger (any type, passed through unchanged)
Output trigger (same value as input)
Widgets One BOOLEAN toggle per discovered environment

Node Preview

The node automatically detects all installed comfy-env environments at startup by:

  1. Scanning proxy class closures (comfy-env's _comfy_env_isolated classes)
  2. Reading the live _WORKER_POOL (already-running workers)
  3. Scanning the pixi filesystem (comfy-env/envs/*)

For each detected environment, a toggle switch appears on the node. Set it to ON (True) and connect any trigger input — when the node executes, the selected environment's subprocess is killed. The trigger value passes through to the output unchanged, so the node can be inserted anywhere in a workflow without affecting data flow.

Usage Example

LoadImage ──► WorkerKeeperManualKill ──► PreviewImage
                   │
             [geometrypack] ◄── True → kills geometrypack worker
             [sharp]        ◄── False → leaves sharp worker alive
             [sam3]         ◄── False → leaves sam3 worker alive

All toggles default to OFF (False). Only explicitly enabled environments are killed when the node triggers. Environments with OFF toggles remain under the control of the automatic background service (Layers 1 & 2).

Mode 3 — Kill All (Keybind)

In addition to the two automated layers and the per-environment node, WorkerKeeper registers a native ComfyUI command that kills all live comfy-env / pixi isolation workers at once. It is triggered by a user-assigned hotkey (no default combo ships, to avoid clashes with other extensions) and gives immediate feedback:

  • Green success toast on the canvas: Killed N isolation worker(s). (or No live isolation workers. when nothing was alive).
  • Console log: [WorkerKeeper] killed N isolation worker(s).
  • ComfyUI log: WorkerKeeper: killed all N worker(s) (manual keybind) — using the same silent kill path as the automated layers, so no "RuntimeError: Worker process died" noise appears.

Assigning a key

  1. Open Settings (gear icon) → Keybindings.
  2. Search for Kill all isolation workers.
  3. Click the key field and press the desired combo (e.g. Ctrl + Shift + K).
  4. Save.

The action requires no confirmation and kills every live isolation worker immediately.

Settings

Setting Location Default Description
Show toast notification Settings → WorkerKeeper category true When off, the hotkey still kills all workers and logs to console, but shows no canvas toast.

The command is also available to other tooling/manual callers via the POST /workerkeeper/kill_all route, which returns {"killed": N} (silent kill, same as the layers).

Benchmark: Impact on System

WorkerKeeper Itself

Operation Frequency CPU Time Blocks Execution?
Module init + hook registration Once at startup < 0.01 ms No
Build env mapping (closure scan) Once (lazy, first prompt) < 1 ms No
Layer 1 scan (no kill) Every prompt ~0.05 ms Yes (negligible)
Layer 2 scan (no kill) Every prompt ~0.05 ms No (async)
Kill one worker Per unused env ~50 ms Varies

Memory Savings per Environment

Environment RAM Freed VRAM Freed (typical)
geometrypack-nodes ~2000 MB ~200-500 MB
gaussianpack-nodes ~2000 MB ~100-300 MB
sam3-nodes ~2000 MB ~500-1500 MB (model)
sharp-nodes ~2000 MB ~500-1000 MB (model)
moge2-nodes ~2000 MB ~300-800 MB (model)
panopack-nodes ~2000 MB ~200-500 MB
depthanythingv3-nodes ~2000 MB ~300-800 MB (model)

Total potential savings: 2.5+ GB system RAM + 2-5+ GB GPU VRAM.

Installation

Via Git URL (ComfyUI Manager)

  1. Open ComfyUI Manager
  2. "Install via Git URL"
  3. Enter https://github.com/PozzettiAndrea/ComfyUI-WorkerKeeper.git

Manual

cd ComfyUI/custom_nodes
git clone https://github.com/PozzettiAndrea/ComfyUI-WorkerKeeper.git

No dependencies. WorkerKeeper only uses Python stdlib + the already-installed comfy-env, server, and comfy_execution modules that ship with ComfyUI.

S
Description
No description provided
Readme
66 KiB
Languages
Python 86.1%
JavaScript 13.9%