Config reference: the two TOML files¶
Two files¶
Two files, two roles (ADR-0003):
-
comfy-env-root.tomlat the pack root:- Declares other nodepacks that this nodepack depends on or uses
- Declares nodepack custom type serializers
-
comfy-env.tomlatnodes/ornodes/<subdir>.- Gives that directory its own isolated pixi environment.
- The file's presence is the isolation switch.
Those two locations are the only ones supported: discovery and the runtime binder deliberately match, so a config anywhere else is simply not seen rather than silently materialized-but-unused.
Example nodepack¶
Using ComfyUI-GeometryPack as the example:
ComfyUI/custom_nodes/
`-- ComfyUI-GeometryPack/
+-- comfy-env-root.toml <- FILE 1: pack-wide. [types], [node_packs].
| Declares no environment.
+-- requirements.txt <- one line: comfy-env. The host env gets
| nothing else.
+-- prestartup_script.py setup_env()
+-- install.py install()
+-- __init__.py register_nodes()
`-- nodes/
+-- comfy-env.toml <- FILE 2: everything below is ONE isolated
| env (cgal, igl, pyvista, trimesh, ...)
+-- boolean/
+-- remeshing/
+-- skeleton/, uv/, io/, analysis/, ... (29 dirs in total)
GeometryPack puts the env file at nodes/, so the whole directory is a
single environment.
The case where a pack needs two or more environments is also supported!
Nodepack authors can put one comfy-env.toml in each nodes/<subdir>/ instead -- one env per subdir,
named <pack>-<subdir>.
comfy-env.toml¶
comfy-env.toml is open schema.
comfy-env translates this .toml file into a pixi.toml config file format.
Every key in comfy-env.toml falls into one of four buckets:
- Ours: comfy-env consumes it and emits something else. Exactly four keys.
- Passthrough: copied into the generated
pixi.tomluntouched. - Rewritten: passed through, but not unchanged. The torch family, plus the
two packages whose pin comfy-env replicates from the host (
comfy-aimdo,comfy-kitchen). - Refused: a hard error, because comfy-env generates it.
| Key(s) | Bucket | Fate |
|---|---|---|
python |
ours | the env's interpreter pin (quoted string, 3.10 minimum) |
[cuda] |
ours | packages resolved to prebuilt wheel URLs at install time, inlined into the manifest as direct-URL pypi dependencies |
[env_vars] |
ours | env vars on this env's workers and scans -- never reaches pixi. These land before comfy-env's own spawn-time writes, and the host-derived writes (args mirror, aimdo enable/version/headroom, COMFY_CPU) are guarded not-in-env, so a value pinned here wins over the host-derived one. That makes it a lever as well as a setting: pinning COMFY_ENV_MIRROR_ARGS=0 here disables the host CLI flag mirror for this pack. A few writes are unconditional and overwrite a pack's value: COMFY_ENV_IPC_ADDR, COMFY_ENV_IPC_AUTHKEY, COMFY_ENV_PARENT_CUDA_IPC, COMFY_ENV_HOST_LOG_LEVEL, COMFYUI_BASE, COMFYUI_ISOLATION_WORKER, PYTHONNOUSERSITE, and PYTHONPATH / PYTHONHOME / PYTHONSTARTUP / PYTHONUSERBASE are removed (subprocess.py, subenv.py). Values are coerced with str(), so { N = 4 } arrives as "4" |
[options] |
ours | runtime knobs -- never reaches pixi. Exactly one exists today: health_check_timeout (seconds, per-env worker ping timeout, default 5.0); call_timeout is planned (ADR-0018) |
[dependencies], [pypi-dependencies], [target.*], [activation], [tasks], [pypi-options], [system-requirements] |
passthrough | forwarded into the generated pixi.toml at feature level ([activation] is merged with comfy-env's own KMP_DUPLICATE_LIB_OK entry; [target.*] keeps only the current platform's table) |
[workspace] |
passthrough, partially | only workspace.channels is read and merged after conda-forge; every other [workspace] key is silently dropped (the generated [workspace] is comfy-env's own) |
torch / torchvision / torchaudio pins |
rewritten | stripped and replaced with the workspace-wide pin, with a log line |
comfy-aimdo / comfy-kitchen pins |
rewritten | replaced with the host ComfyUI's exact pin (or dropped when the host has none, leaving the solver to pick); an exact == pin that disagrees with the host is a ValueError. Both are also injected into every torch env that never declared them (comfy-aimdo only on CUDA stacks) |
[environments], [feature.*] |
refused | compiler-owned: the manifest is single-feature/single-environment by design |
workspace.name / .version / .platforms |
refused | compiler-owned: env identity and host-derived platforms |
[node_packs], [types] |
refused | root-file sections -- the two files do not share a vocabulary |
[serializers] |
refused | removed 0.4.16; declare [types] in the root file instead |
The 4 kinds of keys in comfy-env.toml¶
1. Ours — translated, never forwarded¶
This bucket is exactly four keys, and the list is closed: parse_config
consumes python, [cuda], [env_vars] and [options], and anything it does
not consume is passthrough by definition.
They do not exist in pixi at all. Pixi 0.75.0 accepts exactly seventeen top-level tables:
workspace, package, target, dependencies, host-dependencies,
build-dependencies, constraints, exclude-newer, pypi-dependencies,
pypi-exclude-newer, dev, activation, tasks, feature, environments,
pypi-options, system-requirements
So [cuda] in a manifest handed straight to pixi is a hard error
('cuda' was not expected here). comfy-env consumes these itself and emits
something pixi understands.
| You write | What it becomes | When |
|---|---|---|
python = "3.11" |
[feature.node.dependencies] python = "3.11.*" (the single feature is always named node; the pixi environment is always default) |
build |
[cuda] packages |
resolved wheel URLs, inlined into the manifest as direct-URL [feature.node.pypi-dependencies] entries so they are solved and recorded in pixi.lock (see cuda-wheels) |
build |
[env_vars] |
environment variables set on the worker process at spawn | run |
[options] |
the one runtime knob: health_check_timeout (seconds, default 5.0) |
run |
The bottom two never reach pixi.toml in any form. [env_vars] in particular
is not [activation.env]: it is applied when comfy-env spawns the metadata
scan and the persistent worker, so it affects those processes and nothing else.
Careful with cuda: it is not a pixi table, but it is a valid key
inside [system-requirements]. Different thing, and comfy-env never emits
it: the only system requirements it derives from the host are glibc (Linux)
and macos (macOS), written as keys on the workspace platforms entry, and
only when the pack declared no [system-requirements] of its own. The CUDA
major it detects is used for the torch index choice, not for this key.
2. Passthrough¶
Most native pixi keys like [tasks], [activation], [pypi-options],
[system-requirements], [target.*], [dependencies]... are copied into the
generated pixi.toml at feature level. comfy-env does not validate it;
pixi does, because pixi is pinned (ADR-0002).
Use whatever pixi magic you like.
3. Rewritten on the way through¶
Forwarded, but not unchanged: torch-family pins. torch, torchvision
and torchaudio in [dependencies] or [pypi-dependencies] are stripped and
replaced with the workspace-wide pin, and you get a log line saying so.
The parent and every worker ideally share one identical torch for compatibility and disk space reasons.
The same rule covers the two packages whose version comfy-env replicates
from the host rather than authors: comfy-aimdo and comfy-kitchen
(_HOST_DERIVED_PKGS in toml_generator.py). A pack's declaration in
[pypi-dependencies] (or the current platform's [target.*] table) is
replaced with the host ComfyUI's exact pin, read from the installed
distribution or ComfyUI's requirements.txt; when the host has no pin the
declaration is dropped and the solver picks. A wildcard or open range is
normalised silently; an exact == pin that disagrees with the host raises
ValueError, because a worker and its host must agree on these. Both are
also injected into every env that has torch at all, even when the pack
never declared them, since the host's ComfyUI imports them unguarded on the
comfy.model_patcher import chain (comfy-aimdo is skipped on CPU stacks,
where it has no code path). There is no operator switch for this: the
host_derived parameter exists on build_env_toml, but install_workspace
always leaves it at its default of on.
4. Refused¶
Setting these is a hard error, because using them in comfy-env would not be appropriate:
| Key | Why |
|---|---|
[environments], [feature.*] |
the per-env manifest is single-feature / single-environment by design (ADR-0007) |
workspace.name, workspace.version |
env identity |
workspace.platforms |
derived from the host machine |
comfy-env-root.toml¶
comfy-env-root.toml is closed schema: it can only have two sections and nothing
else. Any other top-level table is a hard parse error.
| Section | What it is |
|---|---|
[node_packs] |
Peer nodepacks to install: git-ref-pinned table form |
[types] |
Wire types this pack puts on sockets: "builtin" or "custom" |
Example:
[node_packs]
OtherPack = { github = "https://github.com/x/OtherPack", tag = "v1.2.0" }
ComfyUI-GeometryPack = "https://github.com/PozzettiAndrea/ComfyUI-GeometryPack"
[types]
TRIMESH = "custom"
SKELETON = "builtin"
INTRINSICS = "builtin"
Notes:
install()consumes[node_packs]register_nodes()validates and loads[types](see custom wire types)
[node_packs]¶
We can declare nodepacks to install together with our main one in various ways, both from the registry and from github.
After cloning/downloading, the peer's own install.py is run (if it has
one). Its requirements.txt is not pip-installed by comfy-env
(packages/node_packs.py has no pip step); a peer that needs host packages
must install them from its own install.py
| Spelling | Example | What happens |
|---|---|---|
| string shorthand | Pack = "owner/Pack" |
owner/repo is normalized to https://github.com/owner/repo; full URLs pass through. Shallow clone of the default branch -- unpinned, deprecated by ADR-0016 |
github + tag |
Pack = { github = "owner/Pack", tag = "v1.2.0" } |
git clone --depth 1 --branch v1.2.0 -- the required shape per ADR-0016 |
github + branch |
{ github = ..., branch = "dev" } |
shallow clone of that branch (moving ref -- unreproducible) |
github + commit |
{ github = ..., commit = "abc1234..." } |
full clone + git checkout <sha> (arbitrary commits cannot be shallow-cloned) |
registry |
{ registry = "pack-id" } |
zip download via api.comfy.org/nodes/<id>/install (latest version) |
registry + version |
{ registry = "pack-id", version = "1.2.0" } |
same endpoint, pinned version |
repo is accepted as an alias for github. tag wins over branch for
the clone (--depth 1 --branch <ref>); commit alone forces a full clone.
The post-clone git checkout <commit> runs whenever commit is set, tag or
branch included -- so { tag = ..., commit = ... } shallow-clones the tag
and then fails the checkout unless the commit is reachable in that
single-depth history.
Note:
Isolation is per-directory, not per-pack: nodes/main/ with no config
imports in-process like any vanilla pack, while nodes/cgal/ with a
comfy-env.toml gets its own env. Put the exotic dependencies behind a
config and leave the lightweight nodes on the host runtime.