fix(run): resolve the bundled default checkpoint at runtime (BE-2994) - #519
fix(run): resolve the bundled default checkpoint at runtime (BE-2994)#519mattmillerai wants to merge 5 commits into
Conversation
`comfy run --prompt` (zero-config) hard-pinned one checkpoint and failed
wherever it was absent — Comfy Cloud and fresh local installs.
- Fix the stale pin: node "4" ckpt_name is now
v1-5-pruned-emaonly-fp16.safetensors (the gallery/tutorial SD1.5 file).
- Add a pure, offline-testable `resolve_default_checkpoint(workflow,
object_info)` in cql/default_workflow.py: if the pinned checkpoint is in the
target's CheckpointLoaderSimple enum → no change; else if the enum is
non-empty → substitute the first available checkpoint + emit a note; else
(positively-empty enum) → flag no_checkpoint; enum absent / object_info {} →
fail open (unchanged behavior).
- Wire it into both run paths (local + cloud) after object_info is fetched and
before preflight, guarded to the bundled default graph and skipped when the
user pinned the checkpoint via --set (honored verbatim). Thread a
checkpoint_user_set flag through the `preloaded` tuple.
- Emit a `checkpoint_substituted` event (--json) / pretty notice on
substitution; raise a new `no_checkpoint_available` error with an actionable
download/cloud hint when the target has zero checkpoints.
📝 WalkthroughWalkthroughChangesThe bundled text-to-image workflow now uses an FP16 safetensors checkpoint and resolves it against runtime checkpoint metadata. Local and cloud execution preserve explicit overrides, substitute unavailable defaults when possible, and report missing local checkpoints with a registered error. Sequence Diagram(s)sequenceDiagram
participant RunCommand
participant Execute
participant ObjectInfo
participant CheckpointResolver
participant Renderer
RunCommand->>Execute: pass workflow and checkpoint_user_set
Execute->>ObjectInfo: load target metadata
Execute->>CheckpointResolver: resolve default checkpoint
CheckpointResolver-->>Execute: updated workflow and resolution result
Execute->>Renderer: emit preview or substitution event
Execute->>Execute: preflight and submit
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 2✅ Passed checks (2 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
✨ Simplify code
Comment |
There was a problem hiding this comment.
🔍 Cursor Review — Consolidated panel
Triggered by @mattmillerai.
Found 7 finding(s).
| Severity | Count |
|---|---|
| 🟡 Medium | 3 |
| 🟢 Low | 4 |
Panel: 6/8 reviewers contributed findings.
Reviewers that did not contribute: kimi-k2.5:adversarial (empty), kimi-k2.5:edge-case (empty)
…2994) Address cursor-review findings on the runtime checkpoint resolver: - Cloud fails open on an empty checkpoint enum instead of hard-erroring no_checkpoint_available: Comfy Cloud provisions models per-job, so a zero-length cached enum can't prove the run would fail. Only the local path (enum reflects what's installed) treats empty as a hard stop. - Resolve the bundled default's checkpoint BEFORE emitting the prompt_preview event in the submit flow so the streamed audit trail advertises the graph we actually submit. --print-prompt stays a no-server-hit dry-run (documented) and prints the graph as-is. - Guard _checkpoint_enum against non-dict object_info (a hostile server returning `[]`) so it fails open rather than crashing with AttributeError. - Match a pinned bare filename against enum entries by basename so a checkpoint living in a subfolder (SD1.5/…safetensors) is found instead of triggering a needless substitution. - Escape the target-provided checkpoint name in the pretty substitution notice to prevent Rich markup injection. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Reviews resolver pass — all 7 review threads are now resolved, and no code changes were needed. Review threads: 5 were already fixed in 93cfdfb. The 2 remaining were deferred/declined with reasoning; rather than leave them as loose unresolved threads, both are now recorded as tracked follow-ups (renderer events in single-envelope JSON; architecture-aware checkpoint substitution) and resolved. Neither is tagged for autonomous pickup — each needs a design decision first. Merge state: The failing
Root cause: the workflow This PR touches only Verified locally: 63/63 PR tests pass ( All other checks green: ruff, build, CodeQL, GPU runners, and Tests on macos/ubuntu/windows 3.10. |
|
🤖 The reviews loop filed Linear follow-up ticket(s) for review thread(s) deferred as out of scope for this PR:
|
|
This PR currently conflicts with Please rebase (or merge |
…default-checkpoint # Conflicts: # comfy_cli/cmdline.py # tests/comfy_cli/command/test_run_prompt.py
|
Rebased on `main` — the merge conflict blocking review (in `comfy_cli/cmdline.py` and `tests/comfy_cli/command/test_run_prompt.py`) is resolved. Both conflicts were between this branch's runtime checkpoint-resolution work and `main`'s simpler static "needs a checkpoint" warning notice (`TestDefaultCheckpointNotice`); kept this branch's runtime resolution (which supersedes the static warning) and dropped main's now-superseded notice/tests accordingly. Verified on the merged tree: full suite (3431 passed / 37 skipped), `ruff check`/`ruff format --diff` clean against the CI-pinned ruff 0.15.15. The failing Windows @bigcat88 conflict is cleared — ready for another look whenever convenient. |
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@comfy_cli/cmdline.py`:
- Around line 949-960: Refresh the --prompt option help text near the prompt
argument definition to reference v1-5-pruned-emaonly-fp16.safetensors instead of
the obsolete checkpoint name, and accurately describe that the CLI may
substitute an installed checkpoint at runtime. Leave the build_default_workflow
and checkpoint override behavior unchanged.
In `@comfy_cli/command/run/__init__.py`:
- Around line 741-749: Reuse the cloud object_info loaded by the UI conversion
branch instead of calling _load_from_target twice. Initialize a shared
object_info dictionary before the is_ui branch, assign the existing cloud
snapshot to it when available, and have the later checkpoint-resolution block
use that value while preserving the empty-dictionary fallback on load failure.
In `@comfy_cli/cql/default_workflow.py`:
- Around line 359-364: Update the basename matching in the checkpoint resolution
logic around the pinned string and enum entries to handle both `/` and `\`
separators. Normalize or split both values using platform-independent separator
handling before comparing basenames, while preserving the existing checkpoint
assignment and early return behavior; add coverage for a backslash-separated
enum entry if tests are available.
In `@tests/comfy_cli/command/test_run_prompt.py`:
- Around line 27-28: Move the shared _object_info_with_checkpoints helper from
tests/comfy_cli/command/test_run_prompt.py and
tests/comfy_cli/cql/test_default_workflow.py into the appropriate shared
conftest.py, then update both test modules to use the centralized helper and
remove their local duplicate definitions.
In `@tests/comfy_cli/cql/test_default_workflow.py`:
- Line 265: Replace the unused out binding in the resolve_default_checkpoint
test with an underscore assignment, while preserving the existing res value and
test behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: a98f9f22-4138-4a69-8c7b-4617eaff458c
📒 Files selected for processing (8)
comfy_cli/cmdline.pycomfy_cli/command/run/__init__.pycomfy_cli/command/run/preflight.pycomfy_cli/cql/data/default_text2img.jsoncomfy_cli/cql/default_workflow.pycomfy_cli/error_codes.pytests/comfy_cli/command/test_run_prompt.pytests/comfy_cli/cql/test_default_workflow.py
…(BE-2994) Four findings from the CodeRabbit pass: - `--prompt` help text advertised the pre-PR checkpoint name (`v1-5-pruned-emaonly.ckpt`) and still claimed the checkpoint "is NOT downloaded for you", with no mention that the CLI now substitutes an installed one at runtime. Refreshed to match actual behavior. - The cloud path fetched `/object_info` twice when the workflow arrived in UI format: once to lower UI→API, then again for checkpoint resolution and preflight. `_load_from_target` is a live, uncached HTTPS fetch, so this was a real second round-trip. Share one snapshot, using `None` (rather than falsiness) as the not-yet-fetched sentinel so a fetched-but-empty snapshot does not trigger a redundant refetch. - Basename matching for a subfoldered checkpoint split on `/` only. ComfyUI's `folder_paths` builds relative names with the SERVER's separator, so a Windows host enumerates `SD1.5\name.safetensors` — the intended checkpoint was missed and `enum[0]` substituted needlessly. Normalize separators on both sides via `_basename`, with coverage. - Dropped an unused `out` binding in a resolver test (Pylint W0612). Declined: hoisting the two-line `_object_info_with_checkpoints` test stub into a shared conftest — see the thread reply.
|
Reviews resolver pass — CodeRabbit's 5 threads are all resolved; 4 fixed in ee688b8, 1 declined with reasoning. Fixed
Declined: hoisting the two-line Verification: full suite 3432 passed / 37 skipped; Status: all 15 CI checks pass, including |
…default-checkpoint # Conflicts: # comfy_cli/command/run/__init__.py
|
Reviews resolver pass — No unresolved review threads found (all 7 CodeRabbit threads from the prior pass were already resolved). Verified on the merged tree: full suite (3669 passed / 37 skipped),
|
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
comfy_cli/command/run/__init__.py (2)
261-268: 🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick winReuse the UI conversion snapshot.
For UI workflows, line 215 already fetched
object_info; lines 266-268 replace it with{}and fetch again. Reuse the successful conversion snapshot so one run does not make two/object_inforequests or validate against a different schema snapshot.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@comfy_cli/command/run/__init__.py` around lines 261 - 268, Update the object_info initialization in the non-print-prompt flow to reuse the successful UI conversion snapshot fetched around line 215, rather than resetting it to an empty dict and calling _fetch_object_info again. Preserve the existing print_prompt behavior of skipping the fetch and leaving the graph unchanged.
821-852: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winKeep cloud
--print-promptserver-independent.This loads cloud
object_infoand resolves/substitutes the checkpoint before checkingprint_prompt. Thuscomfy run --where cloud --prompt ... --print-promptcan contact cloud and print a mutated graph, contrary to the documented dry-run behavior and PR objective. Move the print branch before the snapshot/resolution path; UI-format conversion may retain its necessary schema fetch.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@comfy_cli/command/run/__init__.py` around lines 821 - 852, Move the cloud `print_prompt` branch before the `cloud_object_info` loading and `_resolve_default_checkpoint_or_exit` calls so `--print-prompt` remains server-independent and prints the unmutated parsed workflow. Preserve any necessary UI-format schema fetch, while ensuring snapshot loading and checkpoint resolution only run for requests that will be submitted.comfy_cli/cmdline.py (1)
1192-1200: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winWrap the partner-node warning line.
Line 1199 is 140 characters, exceeding the configured 120-character limit.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@comfy_cli/cmdline.py` around lines 1192 - 1200, Wrap the long partner-node warning expression in the partner_nodes block, preserving the existing Rich escaping and warning text while keeping each source line within the 120-character limit.Sources: Learnings, Linters/SAST tools
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@comfy_cli/cmdline.py`:
- Around line 1192-1200: Wrap the long partner-node warning expression in the
partner_nodes block, preserving the existing Rich escaping and warning text
while keeping each source line within the 120-character limit.
In `@comfy_cli/command/run/__init__.py`:
- Around line 261-268: Update the object_info initialization in the
non-print-prompt flow to reuse the successful UI conversion snapshot fetched
around line 215, rather than resetting it to an empty dict and calling
_fetch_object_info again. Preserve the existing print_prompt behavior of
skipping the fetch and leaving the graph unchanged.
- Around line 821-852: Move the cloud `print_prompt` branch before the
`cloud_object_info` loading and `_resolve_default_checkpoint_or_exit` calls so
`--print-prompt` remains server-independent and prints the unmutated parsed
workflow. Preserve any necessary UI-format schema fetch, while ensuring snapshot
loading and checkpoint resolution only run for requests that will be submitted.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 235f1dcd-b1d9-43d8-a2d8-a6eb7291dd59
📒 Files selected for processing (6)
comfy_cli/cmdline.pycomfy_cli/command/run/__init__.pycomfy_cli/command/run/preflight.pycomfy_cli/cql/default_workflow.pycomfy_cli/error_codes.pytests/comfy_cli/cql/test_default_workflow.py
ELI-5
comfy run --prompt "a fox"(no--workflow) runs a tiny built-in text2imgworkflow. That workflow had one specific checkpoint filename baked in. If the
machine (or Comfy Cloud, or a fresh install) didn't have that exact file, the
run failed — often with a cryptic server-side error. This PR makes the CLI look
at what checkpoints the target actually has and use one, so a zero-config run
produces an image wherever any checkpoint exists — and gives a clear,
actionable error when there are truly none.
What changed
Fixed the stale pin.
cql/data/default_text2img.jsonnode"4"ckpt_nameis nowv1-5-pruned-emaonly-fp16.safetensors(the file thegallery
default.jsonand the text-to-image tutorial use), replacing thestale
v1-5-pruned-emaonly.ckpt.Runtime checkpoint resolution. New pure, offline-testable helper
resolve_default_checkpoint(workflow, object_info)incql/default_workflow.py:available and emit a note;
object_infounfetchable ({}) → fail open (unchangedbehavior — submit and let the server answer).
Wired into both run paths (local
execute+execute_cloud) afterobject_infois fetched and before_preflight_validate. It only acts onthe bundled default graph (
workflow_name == "default_text2img") and isskipped entirely when the user pinned the checkpoint via
--set checkpoint=…/
--set 4.ckpt_name=…(honored verbatim; acheckpoint_user_setflag isthreaded through the
preloadedtuple).Actionable no-checkpoint error. When the target positively has zero
checkpoints, a new registered
no_checkpoint_availableerror (exit 1) with adownload hint (local) / gallery-template guidance (cloud) replaces the cryptic
server reject. Fail-open behavior is preserved when
object_infocan't befetched at all.
Substitution is surfaced as a
checkpoint_substitutedNDJSON event (--json)and a one-line pretty notice otherwise.
Acceptance criteria
default_text2img.jsonusesv1-5-pruned-emaonly-fp16.safetensors; fixtures/tests updated.--set checkpoint=<name>always honored verbatim.no_checkpoint_available+ exit 1;object_info{}→ fail-open submit (unchanged).--where localand--where cloud.resolve_default_checkpoint(present / substituted / empty-enum / user-pinned) + wiring tests;ruff/pytestgreen.Testing
uv run --extra dev pytest tests/comfy_cli/cql tests/comfy_cli/command/test_run_prompt.py tests/comfy_cli/command/test_run.py tests/comfy_cli/command/test_run_json.py tests/comfy_cli/command/test_run_cli.py tests/comfy_cli/output→ 488 passed.ruff format --checkclean on all touched files.Judgment calls / notes
resolve_default_checkpoint(...) -> (workflow, note|None), but a barenotecan't distinguish "pinned present (fine)" from "enum positively empty (error)" — both would beNone. I returned a smallCheckpointResolutiondataclass (note/substituted_to/no_checkpoint) so the caller can drive step 3 precisely and only hard-error when it positively knows the enum is empty.--print-promptis unchanged. The dry-run returns before the server round-trip, so it prints the bundled pin, not the runtime-resolved checkpoint (consistent with--print-prompt's existing "no server hit" contract). Theprompt_previewevent (emitted before resolution) likewise shows the pre-substitution graph; the explicitcheckpoint_substitutedevent documents the swap in the stream.checkpoint_substitutedis not added toschemas/run_event.json'stypeenum — that enum is already non-exhaustive (prompt_preview/convertedaren't in it either;additionalProperties: true), so I matched the established pattern rather than partially expanding it.no_checkpoint_availabledeny path is capability-enabling on net: runtime resolution makes--promptsucceed wherever any checkpoint exists (the opposite of a dead-end). The error fires only in the provably-empty-enum case — a state where the workflow genuinely cannot run (no checkpoint to load) — and its hint redirects to the real fix (install a model / gallery template /--set). Fail-open is preserved for every ambiguous case.Out of scope
Restructuring the bundled default to the gallery's newer Z-Image-Turbo graph (multi-file diffusion) is a larger, separate change and a product call on the blessed default model. Runtime resolution deliberately sidesteps that choice.