Code breakdown -- named and shamed¶
Where the lines actually go. 19,264 lines of Python across 46 files under
src/comfy_env/ (raw wc -l, blanks and comments included).
Snapshot at fe9ff74 (2026-09-12). This page is a photograph and it will
drift, and it has: the snapshot before this one said 18,342 across 44 at
v0.4.38, and the one before that said 12,797 across 38 and was left standing
for 96 commits while the memory floor added most of the difference. Function
lengths below are ast spans (end_lineno - lineno + 1), file lengths are
wc -l. Regenerate with:
find src/comfy_env -name '*.py' | xargs wc -l | sort -rn
By subsystem¶
| Subsystem | Lines | % |
|---|---|---|
Transport / worker IPC (isolation/workers/) |
5,689 | 29.5% |
Node registration / proxy / ComfyUI glue (isolation/ excl. workers, plus server_stub.py) |
5,306 | 27.5% |
Memory floor, shared and worker side (memory_manager, state_sync, reserve, contract, mirrored_args) |
2,621 | 13.6% |
Environment build / install / wheels (install/, packages/, environment/, pixi.py) |
4,062 | 21.1% |
| Hardware detection | 574 | 3.0% |
| Config / CLI / misc | 1,012 | 5.3% |
| Total | 19,264 | 100% |
Two subsystems -- the transport and the ComfyUI glue -- are 57% of the project, and the memory floor is now roughly half the size of either. That is the honest shape of comfy-env: a serialization stack, a ComfyUI adapter, and a manifest compiler, with a memory floor that did not exist a version ago and is already the fastest growing part.
What shrank, and why¶
0.4.27 and 0.4.28 removed 936 lines. A four-reviewer panel proposed ~2,600; two adversarial reviewers then attacked every claim against the ~30-pack fleet, comfy-test, cuda-wheels and live PyPI, and roughly a third did not survive. The survivors, in order of size:
toml_generator.py, 870 -> 451. The entire v0.3 workspace-wide path survived ADR-0007's supersession and was never deleted. Two tells that nobody had called it in a long time: both builders are annotated-> Dict[str, Any]/-> Pathand actually return 2-tuples, andparse_comfyui_requirementshad zero callers even inside its own module.- The worker's by-reference object cache (~103).
_serialize_resultwas the only emitter of a__comfy_ref__frame and its only callers were its own three recursive calls -- so no frame was ever produced, which left_deserialize_inputas an identity tree-walk over every input of every call. - Parent->worker Pool IPC (~89). The sending half went in 0.4.21; the receiving half stayed. Removed as one coordinated edit -- deleting the parent's send without the worker's matching recv would have added a five-second stall to every worker start.
lint.py(130) moved, not fixed. It guessed import names withname.replace("-", "_"), so a top-levelimport faithcontour(from distfaithc-aot) matched nothing and passed silently. Install is the one moment the answer is knowable, sowrite_env_stampnow recordsaccel_importsresolved by asking the env's own interpreter. The check lives in comfy-test now, where it is exact.os.fsyncinwlog(1 line, the cheapest win here). 2.78 ms vs 0.02 ms per log line on ext4, across 92 call sites, 13 of them inside_from_shm's per-node recursion -- 50-100 ms of pure fsync on a typical call, more than the transport it was instrumenting.
The reason it accumulated: ruff was pinned to E9/F63/F7/F82, so F401
was invisible. It is on now, with F841, and it immediately surfaced 20 dead
imports -- six of them hidden behind # noqa: F401 comments that were false
in four separate files, one claiming "re-exported names used below" for a
block where five of thirteen names were untouched.
Transport / worker IPC -- 5,689 lines (the biggest sink)¶
isolation/workers/ only. tensor_utils.py used to be listed here; it lives
one level up and is counted under the ComfyUI glue below, which is why these
rows now sum to the subsystem table's figure exactly.
| File | Lines | What it is |
|---|---|---|
isolation/workers/_persistent_worker.py |
2,762 | The entire worker program, shipped to the far env as source text (ADR-0006). Main loop, transport, faulthandler/watchdog, the print/logger hijack. Still the largest file, and it has grown by more than a thousand lines since the memory floor started landing. But see the footnote -- a large slice of it is not transport at all. |
isolation/workers/subprocess.py |
1,371 | Parent-side SubprocessWorker: spawn, authkey handshake, health, call/echo, consumed-ack, the canary + device-identity checks. |
isolation/workers/_ipc_shared.py |
943 | The shared serialization core both sides import: the _to_shm walker, the registry, OpaquePayload, and the MESH/VOXEL/SPLAT codecs added in 0.4.28. The one comfy_env-import-free leaf. |
isolation/workers/_ipc_parent.py |
526 | Parent transport internals: SocketTransport, tensor strategies, _from_shm. |
isolation/workers/base.py |
73 | Worker ABC + WorkerError + InterruptRequested. |
isolation/workers/__init__.py |
14 | Re-exports. |
Much of _persistent_worker.py is filed here and shouldn't be
A large part of the worker is ComfyUI co-management, not transport: lines
that replace or mutate globals ComfyUI owns -- torch.nn.Module.to/.cuda,
comfy.model_management.load_models_gpu, comfy.cli_args.args -- plus the
far half of the eviction protocol. Re-filing them would move real weight
from Transport to Node/glue.
The exact split is not restated here because it is a hand count and the file has grown 1,023 lines since it was made (1,739 -> 2,762). The old figures were 252 lines of global mutation and 163 of eviction protocol, 24% of the file, measured at the smaller size with line citations that no longer point where they did. Re-deriving them means reading the file, not scaling the old number.
The duplication, measured¶
The previous snapshot said "~600-800 lines that exist twice". Measured with
SequenceMatcher over the eight forked pairs (measured at the v0.4.38
snapshot; not re-run for this one):
| Pair | parent | worker | byte-identical |
|---|---|---|---|
SocketTransport |
62 | 52 | 29 |
_from_shm |
85 | 96 | 37 |
_serialize_cuda_ipc |
53 | 43 | 34 |
_deserialize_cuda_ipc |
37 | 32 | 27 |
_probe_cuda_ipc |
23 | 34 | 20 |
_serialize_tensor_native* |
56 | 44 | 35 |
_deserialize_tensor_* |
52 | 63 | 36 |
TensorKeeper (parent / worker) |
— | 32 | 7 |
| Total | 381 | 396 | 225 |
~388 lines per copy, 777 in total, 225 byte-identical. Read as "per copy", the old figure was roughly double the truth.
And it is not forced. _ipc_parent.py already parses clean at the worker
floor (3.10) and its only comfy_env import is the debug flag. _ipc_shared.py is
already copied next to the worker (subprocess.py) and imported by it, so
the read-as-text delivery demonstrably satisfies cross-module imports today.
What is genuinely side-specific is ~115 lines of policy -- who owns the shm
payload, which keeper, thread-local vs global pool state -- and one real
semantic difference: the two recv() implementations disagree on EOF vs
timeout, and subprocess.py branches on that difference to tell a crash from a
hang. Merge them carelessly and a segfault reports as a ten-minute stall.
Environment build / install / wheels -- 4,062 lines¶
| File | Lines | What it is |
|---|---|---|
install/workspace.py |
1,053 | Workspace materialization: discover configs, resolve torch pin, pick wheel combo, hash for change detection, pixi install per env, stamp. |
environment/cache.py |
794 | Env identity, ABI tags, workspace layout, the Windows LOCALAPPDATA guard. |
packages/toml_generator.py |
683 | The manifest compiler: each comfy-env.toml -> a per-env pixi.toml (ADR-0013). |
packages/cuda_wheels.py |
433 | CUDA wheel index resolution (ADR-0004). |
packages/node_packs.py |
159 | [node_packs] peer-pack install (ADR-0016). |
install/helpers.py |
126 | Install-time helpers. |
environment/libomp.py |
151 | macOS libomp dedupe, and a result record so a pass that fixed nothing says so. |
install/progress.py |
244 | Live pixi install progress derived from the filesystem (conda-meta/*.json and *.dist-info counted against pixi.lock), because pixi self-suppresses its bar when stderr is a pipe. |
install/plugin.py |
26 | The [node_packs] peer-pack step: one function that calls install_node_packs. |
pixi.py |
111 | Pinned, sha256-verified pixi-binary provisioning. |
install/__init__.py |
73 | install() entry. |
environment/runtime.py |
84 | The RuntimeEnv contract behind comfy-env info --json. |
environment/setup.py |
73 | setup_env(): faulthandler, libomp dedupe. |
packages/__init__.py / environment/__init__.py |
25 / 27 | Small. (install/verify.py, listed here at 18 lines in the previous snapshot, no longer exists.) |
The old claim about this section was wrong
The previous snapshot said the two 1,000-line files were "large because the input space (conda + PyPI + CUDA combos x platforms) genuinely is."
toml_generator.py fell to 451 when 412 of the 419 lines deleted
were a dead v0.3 code path -- so 47% of its size at the time was never
input-space complexity at all. It has since grown back to 683 on real
work (wheel inlining, the torch-family rewrite), which does not undo the
point: nearly half of it was once dead. And workspace.py is large mostly
because install_workspace is a single 430-line function: a linear
twelve-phase pipeline nobody has cut into its named phases.
The honest version: the wheel-combo resolver (132 lines) and the per-node feature builder (141) are large because the input space is. The rest is large because it hasn't been split.
Node registration / proxy / ComfyUI glue -- 5,306 lines¶
| File | Lines | What it is |
|---|---|---|
isolation/metadata.py |
1,891 | The scan subprocess + proxy synthesis (ADR-0023) -- the subsystem most exposed to ComfyUI schema churn (V1/V3 duality, DynamicCombo, hidden inputs). |
isolation/pool.py |
1,936 | The worker pool: lifecycle, restart+generations, VRAM/progress callbacks, route proxying, the _STALE_PATCHERS invariant (ADR-0019), and the host half of the memory floor -- which is where nearly all of its 1,355-line growth since the 581-line hand count went. |
isolation/wrap.py |
576 | register_nodes() orchestration. |
isolation/model_patcher.py |
401 | SubprocessModelPatcher -- resident models obey ComfyUI's VRAM manager. |
isolation/subenv.py |
121 | Launch-env construction for the worker subprocess. |
isolation/errors.py |
88 | Error translation across the boundary: a closed vocabulary, never a pickled exception class. |
isolation/tensor_utils.py |
64 | clone-on-foreign-storage for CUDA re-export, madvise reclaim. |
server_stub.py |
110 | The stand-in server module staged beside the worker and the scan: PromptServer.instance forwards send_sync / send_progress_text / client_id, everything else raises with a pointer to the docs. Top-level, stdlib-only, so it parses on the oldest worker Python. |
isolation/procgroup.py |
66 | Run a child in its own process group so a timeout kills its whole tree (killpg on POSIX, taskkill /T on Windows), for the scan under pixi run. |
isolation/__init__.py |
34 | Re-exports. |
The monkey-patch surface, recounted¶
The previous snapshot claimed 2,719 lines the upstream RFC would let
comfy-env delete, and said every ComfyUI internal comfy-env touches lives in
wrap.py, pool.py, metadata.py and model_patcher.py. That number was
those four files' sizes added together. Both halves are wrong.
The right-hand columns are a hand count from an older tree
"Actually touches ComfyUI" was measured by reading each file, so it cannot
be regenerated by wc. The Total column below is refreshed; the
measured column and its share are as of the v0.4.31 snapshot and are
shown against the totals they were taken from. Do not read a share as
current -- pool.py alone has more than tripled since. The shape of the
finding survives (the file named as the monkey-patch surface, wrap.py,
touches almost nothing); the percentages do not.
| File | Total now | Total when measured | Touched ComfyUI (at that size) | Share then |
|---|---|---|---|---|
metadata.py |
1,891 | 1,265 | ~904 | 71% |
_persistent_worker.py |
2,762 | 1,739 | 415 | 24% |
pool.py |
1,936 | 581 | ~292 | 50% |
model_patcher.py |
401 | 277 | 277 | 100% |
environment/cache.py |
794 | 620 | ~91 | 15% |
workers/subprocess.py |
1,371 | 1,092 | ~59 | 5% |
_ipc_shared.py / _ipc_parent.py |
1,469 | 1,561 | ~54 | 3% |
wrap.py |
576 | 585 | 4 | 0.7% |
~2,100 lines across nine files, not 2,719 across four. wrap.py --
which contributed 590 to the old claim -- reaches into ComfyUI in exactly one
place: it reads folder_paths.base_path to find the Desktop app's user data
directory. Meanwhile the transport files
and environment/cache.py contribute ~200 lines the four-file framing missed
entirely.
And the three-hook RFC would retire far less than even 2,100: roughly
1,150-1,400. metadata.py's 419-line embedded scan script
(_METADATA_SCRIPT) and its 264-line fetch_metadata survive any upstream
hook, because scanning a pack's nodes
in the far env is comfy-env's job, not ComfyUI's.
Config / CLI / misc -- 1,012 lines¶
| File | Lines | What it is |
|---|---|---|
cli.py |
578 | The comfy-env CLI. The only file whose size is user-facing surface rather than internal machinery -- though ~240 of it is the debug-settings TUI (curses plus a plain-text fallback) containing a 100-line nested draw, and cmd_gc is another 114. |
config/__init__.py |
189 | The TOML config layer (ADR-0003, ADR-0015). |
settings.py |
79 | Tombstones for the settings removed in 0.4.25, and nothing else. The live settings are env vars read at their point of use; there is no settings file and no resolution layer. |
__init__.py |
101 | Package surface + the three-call contract re-exports. |
debug.py |
65 | Debug categories. |
Hardware detection -- 574 lines¶
| File | Lines | What it is |
|---|---|---|
detection/gpu.py |
270 | The NVML -> nvidia-smi -> PyTorch -> sysfs fallback chain (nvidia-smi first on Windows, where a DLL failure in pynvml or torch can take the process down). |
detection/cuda.py |
132 | CUDA version probing. |
detection/__init__.py |
84 | Platform helpers + the (os, machine) -> pixi platform table. |
detection/backend.py |
70 | Backend selection. |
detection/arch.py |
18 | CPU architecture, which the tier-2 wheel fallback is keyed on. |
The smallest subsystem, and the one that best matches its job size.
The current shame¶
Ordered by severity, not size.
1. The RPC envelope exists in triplicate -- and it has already cost a bug.
call_method (103 lines), call_module (49) and echo (32) in
subprocess.py all run the identical sequence: lock, _ensure_started,
_to_shm, send, error-check, _from_shm, consumed-ack, cleanup. In 0.4.28 a
leak was fixed where echo() omitted the _cleanup_ipc_cache() its two
siblings call, leaving CUDA-IPC entries unevicted on every worker start.
Three copies of one function is why nobody noticed.
2. main() in the worker is one 1,957-line function,
containing 36 nested defs (and two classes) totalling about 910 lines. It
was 1,608 lines at the previous snapshot and 863 at the one before, and it has
not been split; it has more than doubled in two snapshots. ADR-0006
justifies the module being one program shipped as source text. It does not
justify the function. Nothing can move to another file while it closes over
main's locals -- so this is the gate on every other worker-side cleanup, and
it is getting worse rather than holding.
3. A dead decoder for a wire format nothing emits. Beside
_to_shm/_from_shm the worker still carries _deserialize_isolated_objects,
a decoder for __isolated_object__/__attrs__/__path__ frames. Its last
producer was the self_state call, and self_state now rides the
state_sync wire; no file under src/ emits any of those three keys.
Yet the worker runs the decoder over every input of every call -- the
same guaranteed-identity walk 0.4.27 deleted for __comfy_ref__, still
present for a different tag family, and now with nothing on the other end at
all. The one residual disagreement it encodes is about Path: _to_shm
emits a bare string, the dead decoder expects {"__path__": ...}.
4. The wire format carries four tag conventions at once -- a __type__
discriminator, sentinel keys that are the tag, ride-along flags on a
neighbouring frame, and a registry namespace inside __shm_custom__. One key
carries two value types: __shm_np__ is True on the fd path and the block
name on the copy path, so a reader must check "fd" in obj first or hand
True to SharedMemory(name=...).
5. Two TensorKeeper classes, two lifetimes. The worker's honours
ADR-0032's consumed-ack release. The parent's keeps shm inputs and CUDA
re-export clones from their serialization until end_call() empties it,
with the 60 s TTL as the crash fallback. A third, in tensor_utils.py, was
deleted on 2026-09-12: it held every call's inputs and results and pruned
only on the next keep, so the last call's tensors stayed pinned while
ComfyUI idled, protecting nothing (torch's CUDA IPC has its own
cross-process refcount).
6. Both registration paths in wrap.py are copy-pasted -- the
root-level nodes/comfy-env.toml branch and the per-subdir _scan_isolation
closure each call fetch_metadata and build_proxy_class with their own
copy of the surrounding bookkeeping. The metadata.py half of this item is
gone: the V1 and V3 proxy call bodies, which shared 50 identical lines, now
go through one keyword-only _call_in_worker.
7. A fourth NVML probe lives in pool.py (_device_total_bytes, a
pynvml-only read of the torch device's total VRAM), re-implementing what
detection/gpu.py already does. There is no layering excuse: the
package-layers contract makes isolation -> detection a legal edge, and
metadata.py already uses it.
8. Ten functions of 150+ lines account for 4,400 lines, 23% of the
codebase -- main (1,957), register_nodes (430), install_workspace
(430), _ensure_started (405), fetch_metadata (264), build_proxy_class
(204), _build_v3_proxy_class (204), _to_shm_generic (184),
_handle_vram_budget (172) and maybe_enable_aimdo (150). Without main
the other nine are still 2,443 lines, 13%.
What is correctly big¶
_persistent_worker.py's non-ComfyUI majority (~1,320 lines at the last hand count, more now). It ships to the far interpreter as source text and must parse under the oldest worker Python (3.10). A whole program in one module is the right shape. A whole program in one function is not -- see shame #2._to_shm_generic(184). One walker for both sides, twelve type rungs, cycle detection, pluggable tensor strategy. This is the de-duplication that already worked; the residual fork above is what's left._resolve_wheel_combo(132). conda x PyPI x CUDA x torch x CPU-arch with a per-arch tier-2 fallback validated against a live index. The input space genuinely is this shape.model_patcher.py(401), all of it. Every one of its methods exists becausecomfy.model_managementcalls it. Deliberately not aModelPatchersubclass (ADR-0035) -- duck-typing is why it is honest rather than fragile.detection/gpu.py(270). Four detection methods with a platform-dependent order, because pynvml can crash the host process on Windows. Graceful degradation (ADR-0008) costs lines by design.environment/cache.py's identity core. Two envs that hash the same and shouldn't is a silent wrong-torch bind.
What the shape says¶
- Delete-shaped work still exists, but it is smaller and better located than this page used to claim. ~777 doubled transport lines (~388 per copy) and ~2,100 lines of host coupling, of which an upstream RFC would retire perhaps 1,150-1,400.
- The compiler and transport are the problem domain, but that sentence was
laundering two different things. The wheel resolver and the serialization
ladder are irreducible. A 430-line install function and a 1,957-line worker
main()are not. - The gate that lets this regrow is CI configuration, not architecture.
936 lines of dead code accumulated behind a linter that could not see unused
imports, and four
# noqacomments that asserted a use which did not exist. That gate is closed now. The next 936 will need a different excuse.