Static linter for ComfyUI API-format workflow JSON - catch broken workflows before they reach the GPU.
Project description
comfy-lint
You queued a workflow, waited 12 minutes on the GPU, and it failed because a node type didn't exist.
comfy-lint catches that in well under a second.
It reads your ComfyUI API-format workflow JSON, compares it against the /object_info schema of a real ComfyUI install, and tells you exactly which node and which field is wrong — before anything is queued, before a model is loaded, before a single step is sampled.
- Pure standard library. No dependencies. Python 3.9+.
- Works offline in CI with a cached schema — no ComfyUI process required.
- Human output for your terminal,
--jsonfor your pipeline.
Install
PyPI release pending — install from git for now:
pip install git+https://github.com/tangyistudio/comfy-lint
Or from a checkout:
pip install .
Usage
comfy-lint my_workflow.json
That's it. comfy-lint reads the schema from http://127.0.0.1:8188 by default; point it elsewhere with --server.
comfy-lint my_workflow.json --server http://comfy.lan:8188
The file must be API format — in ComfyUI, use Workflow > Export (API). If you hand it a saved UI workflow,
comfy-lintsays so instead of producing nonsense.
Sample output
$ comfy-lint broken_workflow.json --server http://127.0.0.1:8188
broken_workflow.json:2:clip error type-mismatch CLIPTextEncode: 'clip' expects CLIP but node 1 output #0 (CheckpointLoaderSimple) produces MODEL
broken_workflow.json:4:tile_size warning extraneous-input EmptyLatentImage: unknown input 'tile_size' for EmptyLatentImage - it will be ignored
broken_workflow.json:5:denoise error missing-required KSampler: missing required input 'denoise' (expects FLOAT)
broken_workflow.json:5:sampler_name error invalid-enum KSampler: 'dpmpp_3m_sde' is not a valid value for 'sampler_name'; allowed: 'euler', 'euler_ancestral', 'heun', 'dpmpp_2m', 'dpmpp_2m_sde', 'ddim', ... (1 more)
broken_workflow.json:6:vae error dangling-link VAEDecode: 'vae' links to node '12', which does not exist in this workflow
broken_workflow.json:7:class_type error unknown-node SuperSaveImage: unknown node class_type 'SuperSaveImage' - not installed on this ComfyUI (did you mean: SaveImage?)
5 errors, 1 warning in 1 file
schema: http://127.0.0.1:8188/object_info
Anchors are file:node_id:field, so the node id maps straight onto the ids in your JSON. The file part is the path relative to your working directory — the same string in --json — so an editor or a CI annotation can jump straight to it.
A clean run is quiet:
$ comfy-lint my_workflow.json
1 file clean (0 issues)
schema: http://127.0.0.1:8188/object_info
Rules
| Rule | Severity | What it catches |
|---|---|---|
unknown-node |
error | class_type isn't installed on the target server — the classic "missing custom node" failure. Suggests close matches. |
missing-required |
error | A required input is absent, so ComfyUI rejects the prompt. |
invalid-enum |
error | A literal value that isn't one of a COMBO widget's choices — a checkpoint, LoRA or sampler name that doesn't exist. |
dangling-link |
error | An input links to a node id that isn't in the workflow, or to an output slot the upstream node doesn't have. |
type-mismatch |
error | A link whose upstream output type doesn't match the input slot (e.g. MODEL into a CLIP input). |
extraneous-input |
warning | An input the node class doesn't declare; ComfyUI silently ignores it, which is usually a typo or a version drift. |
malformed-node |
error | The node entry isn't a valid API-format object at all. |
Print the table any time with comfy-lint --list-rules.
Exit codes
| Code | Meaning |
|---|---|
0 |
Clean (warnings only, unless --strict) |
1 |
Errors found |
2 |
Usage error, unreadable workflow, or the schema could not be fetched |
--strict promotes every warning to an error, so extraneous-input also fails the build.
When several files are given, an unreadable one is reported on stderr and the remaining files are still linted — you get the whole picture in one run. Exit code 2 wins over 1 in that case, since a file that was never checked is the more urgent problem.
Offline mode / CI
CI runners don't have a GPU or a ComfyUI process. Cache the schema once, commit it, and lint forever without a server:
# On a machine that can reach ComfyUI: fetch and save the schema.
comfy-lint my_workflow.json --schema-cache .comfy/object_info.json
# Anywhere else (CI, pre-commit, an airplane): the cache is used, no network.
comfy-lint my_workflow.json --schema-cache .comfy/object_info.json
If --schema-cache points at a file that exists, it is used and no server is contacted. If the file doesn't exist, the schema is fetched from --server and written there. Use --refresh-cache to refetch after installing new custom nodes.
Both options also read from the environment, which keeps CI configs short:
export COMFY_LINT_SCHEMA_CACHE=.comfy/object_info.json
export COMFY_LINT_SERVER=http://127.0.0.1:8188
GitHub Actions
name: lint-workflows
on: [push, pull_request]
jobs:
comfy-lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
# PyPI release pending; install from git.
- run: pip install git+https://github.com/tangyistudio/comfy-lint
- run: comfy-lint workflows/*.json --schema-cache .comfy/object_info.json --strict
pre-commit
# .pre-commit-config.yaml
repos:
- repo: local
hooks:
- id: comfy-lint
name: comfy-lint
entry: comfy-lint --schema-cache .comfy/object_info.json --strict
language: python
# PyPI release pending; install from git.
additional_dependencies: ["comfy-lint @ git+https://github.com/tangyistudio/comfy-lint"]
files: ^workflows/.*\.json$
comfy-lint accepts multiple files in one invocation, which is exactly what pre-commit passes it.
Machine-readable output
comfy-lint my_workflow.json --schema-cache .comfy/object_info.json --json
{
"tool": "comfy-lint",
"version": "0.1.0",
"schema_source": ".comfy/object_info.json",
"strict": false,
"summary": { "files": 1, "errors": 2, "warnings": 0, "unreadable": 0 },
"files": [
{
"path": "my_workflow.json",
"errors": 2,
"warnings": 0,
"diagnostics": [
{
"severity": "error",
"rule": "invalid-enum",
"node_id": "5",
"class_type": "KSampler",
"field": "sampler_name",
"message": "'dpmpp_3m_sde' is not a valid value for 'sampler_name'; allowed: 'euler', ..."
}
]
}
],
"unreadable": []
}
unreadable lists any file that could not be read or parsed ({"path": ..., "error": ...}) so a --json consumer can tell "clean" apart from "never checked".
Options
comfy-lint [-h] [--server URL] [--schema-cache PATH] [--refresh-cache]
[--timeout SECONDS] [--json] [--strict] [--color {auto,always,never}]
[--list-rules] [--version]
workflow.json [workflow.json ...]
Use it as a library
from comfy_lint import lint_workflow, load_schema
from comfy_lint.linter import load_workflow
schema = load_schema(cache_path=".comfy/object_info.json")
for diagnostic in lint_workflow(load_workflow("my_workflow.json"), schema):
print(diagnostic.severity, diagnostic.anchor("my_workflow.json"), diagnostic.message)
Every rule in comfy_lint.rules is a plain function rule(node_id, node, ctx) -> list[Diagnostic], so adding a project-specific check is a dozen lines and a tuple entry.
Development
pip install -e ".[dev]"
python -m pytest tests/
The test suite is fully offline: it lints the fixtures in tests/fixtures/ against a small hand-written object_info sample.
License
MIT — see LICENSE.
Built by Tangyi Studio
Project details
Release history Release notifications | RSS feed
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_lint-0.1.0.tar.gz.
File metadata
- Download URL: comfy_lint-0.1.0.tar.gz
- Upload date:
- Size: 22.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1429051768608c8e57b598f02079a23ba348d4ffa7a863d1987c059f5fe9cd77
|
|
| MD5 |
e726ee2726307c458c7ca31ce74b6f3b
|
|
| BLAKE2b-256 |
742f9eeb6ce370708e3b0e36dfada322ad0920a41ed87edf3db0402835d51056
|
Provenance
The following attestation bundles were made for comfy_lint-0.1.0.tar.gz:
Publisher:
release.yml on tangyistudio/comfy-lint
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
comfy_lint-0.1.0.tar.gz -
Subject digest:
1429051768608c8e57b598f02079a23ba348d4ffa7a863d1987c059f5fe9cd77 - Sigstore transparency entry: 2255665426
- Sigstore integration time:
-
Permalink:
tangyistudio/comfy-lint@c678391fde1fbee9f8569b42f60aa458b9b11d14 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/tangyistudio
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@c678391fde1fbee9f8569b42f60aa458b9b11d14 -
Trigger Event:
push
-
Statement type:
File details
Details for the file comfy_lint-0.1.0-py3-none-any.whl.
File metadata
- Download URL: comfy_lint-0.1.0-py3-none-any.whl
- Upload date:
- Size: 17.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e96667dc7e2f2c69c2720f7e74737a44adb9bba3c67dd01ab56019549c31f562
|
|
| MD5 |
5ca42e712394b1cfc02ed61dfb3ed467
|
|
| BLAKE2b-256 |
b1eb11eed01269b729474b0c8ae0c3c5912d0f2ed99ba5eba9b6d0ced27650c3
|
Provenance
The following attestation bundles were made for comfy_lint-0.1.0-py3-none-any.whl:
Publisher:
release.yml on tangyistudio/comfy-lint
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
comfy_lint-0.1.0-py3-none-any.whl -
Subject digest:
e96667dc7e2f2c69c2720f7e74737a44adb9bba3c67dd01ab56019549c31f562 - Sigstore transparency entry: 2255665435
- Sigstore integration time:
-
Permalink:
tangyistudio/comfy-lint@c678391fde1fbee9f8569b42f60aa458b9b11d14 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/tangyistudio
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@c678391fde1fbee9f8569b42f60aa458b9b11d14 -
Trigger Event:
push
-
Statement type: