日本語 | 中文 | Español | Français | हिन्दी | Italiano | Português (BR)
A gate that runs on a ComfyUI workflow graph in the seconds before it is submitted,
and halts a submission that would spend credits producing a known-wrong result.
It does not submit. It does not fix your graph.
The gap it lives in
A Comfy Cloud
dry_runPASS does not prove link sanity. A hand-retyped payload with a self-referencing node link —VAEDecode.samples = ["14", 0], the node pointing at itself — returnedstatus: validated.
The provider's validator answers is this graph well-formed enough to run. It does not answer
is this the graph you meant. Every check here lives in that gap, and each one was paid for by
a run that got past dry_run.
Submission is the irreversible act with no real undo, and a preflight is a compensator you run BEFORE instead of after. A completed cloud job is billed; the only compensator afterwards is cancel it if it is still queued, otherwise none. That is the whole argument for this package.
Install
npx @mcptoolshop/comfy-preflight check graph.json # no Python needed
pip install comfy-preflight # for the in-process gate
pip install "comfy-preflight[mcp]" # + the MCP stdio server
The npx launcher downloads a binary from this repo's GitHub Release and verifies its SHA256
against the checksums in that same release before running it. Python ≥ 3.11; binaries for
linux-x64 and win-x64 (macOS installs via pip).
Use it
The production gate — in-process, on the submit path:
from comfy_preflight import preflight
# Inside the function that submits. Not in a shell step before it.
preflight(graph, register, input_dims=(width, height)) # raises PreflightHalt
submit(graph) # only reached if nothing raised
The development door:
comfy-preflight check graph.json \
--input-dims 1072x1024 --register subject.json --saved sidecar.json --json
| exit | meaning |
|---|---|
0 |
nothing halted — PASS, ADVISORY or NOT_APPLICABLE, each named in the output |
1 |
HALT |
2 |
nothing was examined — bad arguments, unreadable file, or an internal error |
ADVISORY exits 0 on purpose: a nonzero status stops a && chain, which would turn an advisory
into a halt in every shell that runs one.
What it checks
| # | check | halts on |
|---|---|---|
| 1 | Link topology | a node input reading its own node; a link to a node id not in the graph |
| 2 | The inverted register scan | a declared register that does not match the graph's construction — in both directions |
| 4 | Saved-is-submitted | the saved sidecar and the submitted payload differing as parsed graphs |
| 5 | Generator-legal frame | a dimension the effective frame's VAE cannot decode at |
| 8 | Declared envelope | nothing — it never halts. Out-of-band is an ADVISORY |
Three checks from the original design are not built, and are named rather than quietly missing: recipe-vs-profile agreement (no subject-profile fixture exists), estimate-before-submit (transport-side — no graph-structural operand), and anchor reproduction (needs the graph builder; the corpus holds outputs, not the scripts that made them).
Check 2 — the one that names the method
When a subject's register declares no style adapter, the claim being asserted is not "the weight is 0.0". It is that no loader node and no adapter card reference exist anywhere in the graph.
A weight of 0.0 is not a weight of zero on a loaded card; it is no card.
And it asserts the mirror image. A decided positive weight with no loader node is silently inert: the run completes, costs money, and produces base-model output while every log line says the adapter was requested. That direction produces no signal a human could notice — which is exactly where a gate earns its place over a person looking.
Check 5 — the effective frame, not the declared one
1066 / 8 = 133.25 encodes to 133 latent columns and decodes to 1064, putting every output
2 px off its control image and breaking every downstream pairing.
The defect happened upstream of the graph. The 1066 was derived correctly from a mesh, the image was rendered at that width and uploaded, and the graph never declared it — so a check reading graph literals could not have caught the incident that motivates it. The operand is the frame the run will actually produce, which on an img2img graph is the input image's dimensions.
÷8 halts. ÷16 advises. A floor and a preference, not two floors.
Check 8 — advisory, and honest about what it cannot say
For each checkpoint the graph loads, parameters are compared against a cited envelope table. Every entry carries its band, source URL, retrieval date and a quote of the card's own words — and the constructor refuses to build an entry that does not.
The day-one entry is Qwen-Image-InstantX-ControlNet-Union. It carries the
controlnet_conditioning_scale band its card documents, and a declared absence for denoise,
because that card publishes no denoise range at all — verified against the live card rather than
recalled. So a run on a graph at denoise=0.92 reports the 0.92, names the parameter, and says
plainly that it cannot judge it and why.
Reporting the value it cannot judge is the honest half of the finding. Inventing a band to judge it against would be the dishonest half.
The adoption contract
Call it in-process on the submit path. There is no skip flag.
The check lives inside the tool that performs the irreversible step. A preflight in a shell chain before a submit is a transport, not a guard — in the incident that produced this rule, 47,020 texels were committed after a gate had already fired, because a PowerShell chain walked past a failing exit code. Nobody decided to proceed; the construction was incapable of stopping.
The CLI and the MCP server are both transports, not gates. Reading a HALT from either and then submitting from somewhere else is that same chain with a nicer interface.
No function here takes a skip, force, warn_only or enabled parameter, and tests read each
signature rather than trusting the docs. Every gate raises; none is a bare assert —
python -O deletes assert silently, and CI runs the whole suite under -O and
PYTHONOPTIMIZE=1 to prove the gates survive it.
Over MCP
python -m comfy_preflight.mcp_server # stdio
npx @mcptoolshop/comfy-preflight mcp # same server, no Python required
One tool, preflight, returning the same structured result the library returns — a test asserts
byte-identity. A HALT is a successful call returning {"verdict": "halt", ...}, because
reporting it as a protocol error would throw away the structure the caller needs.
Security
No network calls, no credentials, no telemetry, and it writes nothing anywhere. It reads a graph and a profile, and returns a verdict. Full threat model — data touched, data not touched, and the permissions required — in SECURITY.md.
Documentation
📖 The handbook — getting started, every check in detail, the adoption contract, and the envelope table.
License
MIT — see LICENSE.
Built by MCP Tool Shop
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file comfy_preflight-1.0.0.tar.gz.
File metadata
- Download URL: comfy_preflight-1.0.0.tar.gz
- Upload date:
- Size: 93.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a5ebdf399dcba09e246404e517381007d4560988556e8552da7e5e60924dd1f3
|
|
| MD5 |
f0a284708c5fe8a6ee698abcaf1d7554
|
|
| BLAKE2b-256 |
a6601694aab9860ea57886e6a777c356826c74f83af8ac9a8e90d8761f73a47c
|
Provenance
The following attestation bundles were made for comfy_preflight-1.0.0.tar.gz:
Publisher:
release.yml on mcp-tool-shop-org/comfy-preflight
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
comfy_preflight-1.0.0.tar.gz -
Subject digest:
a5ebdf399dcba09e246404e517381007d4560988556e8552da7e5e60924dd1f3 - Sigstore transparency entry: 2468826785
- Sigstore integration time:
-
Permalink:
mcp-tool-shop-org/comfy-preflight@2c072fa82ae22d82f0d419a8766e24ca20d98a95 -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/mcp-tool-shop-org
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@2c072fa82ae22d82f0d419a8766e24ca20d98a95 -
Trigger Event:
push
-
Statement type:
File details
Details for the file comfy_preflight-1.0.0-py3-none-any.whl.
File metadata
- Download URL: comfy_preflight-1.0.0-py3-none-any.whl
- Upload date:
- Size: 59.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b93a58261fbb05989e2e5dc12c34c65ce9375a914769f11a20795c08f4156b54
|
|
| MD5 |
056650843fa935aa9b916a0b9ac4d9c7
|
|
| BLAKE2b-256 |
a33e50e4593c1acc0b26233eeb79d25d55dcd371fe8e0c3053e7d8ff0fecde45
|
Provenance
The following attestation bundles were made for comfy_preflight-1.0.0-py3-none-any.whl:
Publisher:
release.yml on mcp-tool-shop-org/comfy-preflight
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
comfy_preflight-1.0.0-py3-none-any.whl -
Subject digest:
b93a58261fbb05989e2e5dc12c34c65ce9375a914769f11a20795c08f4156b54 - Sigstore transparency entry: 2468826800
- Sigstore integration time:
-
Permalink:
mcp-tool-shop-org/comfy-preflight@2c072fa82ae22d82f0d419a8766e24ca20d98a95 -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/mcp-tool-shop-org
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@2c072fa82ae22d82f0d419a8766e24ca20d98a95 -
Trigger Event:
push
-
Statement type: