About showed only version/commit/branch. Add the facts you actually want to
check on a fresh rental: chip, memory, GPU (name, VRAM, compute capability),
free disk, OS, ComfyUI, Python, torch (+CUDA), and which fast-attention
backend is importable — the last one answers "is sage actually installed
here" without reading the launch args.
Everything describes the machine ComfyUI runs on, not the browser. On a
rental those differ, and the host is the one worth looking at, so the panel
says so in a line under the facts.
Every probe is individually guarded and degrades to a dash: About is the one
screen that should still render when the install is broken, so no missing
psutil, torch, or shell binary may raise. The frontend optional-chains the
call, so a frontend sharing settings_window.js without the endpoint keeps the
old version-only panel instead of erroring.
Facts move to a two-column grid; values now range from "3.14" to a full GPU
name, and only a shared column keeps the keys aligned.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
LTX 2.5 reuses LTXAVModel/LTXBaseModel behind config flags, so the samplers,
the identity-transfer wrappers and the guide path all bind to it unchanged.
Two places assumed 2.3 specifics, and both would have gone wrong quietly.
The AV channel split was a hardcoded width table. From 2.5 on comfy reads the
video/audio projection widths off the checkpoint rather than assuming them, so
a checkpoint can ship a pair the table has never seen — and an unrecognised
width made ltxav_video_channels return the FULL width, which silently steers
the audio text context along with the video one. The only symptom would have
been degraded audio. The split is now measured off the live model when one is
on hand; the table stays as the fallback.
Decode-time noise only ever reached VideoVAE.decode. 2.5's diffusion decoder
takes no timestep and seeds itself, but it is an nn.Module, so stamping the
attributes onto it succeeded and was then ignored — a knob that reads as live
and does nothing. It is now capability-tested against the real contract
(hasattr decode_timestep, not a class name) and says so when it cannot apply.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
It shipped in Engine ▸ Experimental, which is not where anyone looks after
picking a sampler — with KSampler selected there was no ALG control in sight.
Moved to its own group in Engine ▸ Sampler algorithm, directly under the
sampler panel, and the Distilled Flow section now says the two ALG switches
are the same guidance so only one of them is needed. Guide/JoyAI blur stays
under Experimental, with a pointer to it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three things that were each locked to one implementation detail:
- steps + schedule were a KSampler-branch feature, though every sampler here
walks a sigma list handed in from outside. They are now pass-level, so
Distilled Flow and Hybrid Euler 2S can run a named schedule (and the second
pass can be computed rather than typed) in both frontends and on the node.
- the project's Frames field snapped to the model's grid on every family. On
H3 that is required, but its arrows moved by 1 and the snap put the value
straight back; on LTX the builder rounds up by itself, so the snap only
fought the user. H3 now steps a whole 17 frames per arrow, LTX keeps what
you type and walks 8k+1 with its arrows.
- ALG lived inside the Distilled Flow step loop, so it was unreachable with a
stock KSampler. Its blurred/sharp swap is decided by the step's sigma, and
sigma is an argument of every model call, so a denoiser proxy drives it from
outside any sampler. New alg_anchor toggle on the Chain Sampler drives the
in-loop version too, so there is one control wherever you are.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A CONDITIONING wired into positive_conditioning is a deliberate setup —
the prompt is encoded somewhere else before it reaches Studio. Warning
about it (and about a CLIP sitting alongside it) told users their working
pipeline was broken, which it isn't.
The chip now fires for the one case that cannot generate anything: no
text encoder on Studio · clip AND nothing on positive_conditioning.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The empty '"prompt" is empty' warning on a wired MiniMax H3 fl2va/ref2va
node was the whole story: with a CONDITIONING wired into Studio and no
CLIP, Studio takes that encoding as-is, so what generates is the wired
node's OWN prompt field — the project prompt never reaches the model.
Three parts:
* Linked inputs gain the missing project texts: anchor, postfix, and
prompt+postfix, alongside the prompt and negative prompt they already
had. The global prompt already carries the anchor; the postfix is the
only piece outside it.
* Every linked TEXT is now shortcut-expanded and $variable-resolved
before it reaches the node, in Studio's order (variables last). A node
outside Studio does neither, so the raw text would encode the literal
/trigger and $name. The revolver is peeked, not committed — the run's
own expansion is what advances the cycle. Studio's own port still gets
the raw text and expands it per scene itself.
* A chip beside Generate when the wiring makes one of the two prompts
inert: no CLIP means the project prompt is not being encoded at all,
a CLIP alongside means the wired conditioning is dropped. Both used to
cost a generation to discover.
Also: an empty widget that is driven by a link no longer warns — blank
is its normal stored state.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
MiniMaxH3ImageToVideo and MiniMaxH3ReferenceToVideo emit their own AV
latent next to their conditioning, so an Empty MiniMax H3 AV Latent is
not needed alongside one. The requirements panel only counted a slot
filed under the empty_latent ROLE, so the row read as missing for anyone
who added the node as what it mostly is — a conditioning source.
A requirement now counts any slot that genuinely SOURCES the type
(outputs it without consuming one), whatever role it carries. VAE keeps
the role check: it is asked for twice and only the role separates the
video decoder from the audio one. A pass-through patcher (a LoRA on
MODEL/CLIP) still never satisfies a requirement by itself.
The builder needed no change here — it already auto-wires the node's
LATENT into the sampler's latent_template.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Studio encodes through clip, but a pre-encoded CONDITIONING on
positive_conditioning replaces that path entirely. The Editor still
treated Studio.clip as unconditionally required, so a project that
brought its own conditioning was blocked — and with two text encoders
installed it was blocked on 'N possible sources, wire it explicitly'
for a port it did not need.
The port is now waived when its alternative is wired, and deliberately
left unwired in that case: auto-wiring a CLIP would take precedence
over the user's own conditioning and silently win.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every Editor preference was per-MACHINE: autocomplete, shortcut ideas, the anchor
toggle and the custom-i2v bypass in browser localStorage, and the shortcut
revolver in a server-side sidecar next to the shortcut DB. A freshly rented box
has neither, so all of them silently revert to their defaults and have to be set
by hand again — the revolver's Random mode every single time.
They now ride along in the project file (Project.editor_settings), and opening a
project puts them back. The revolver is still a server setting shared by every
browser and by generation; the project only restores it, and only POSTs when the
value actually differs, so opening a project that already agrees never restarts a
live cycle.
Two cases that would otherwise leave the feature useless for an existing setup:
* A project saved before this existed, opened on a configured machine, adopts
the machine's current settings instead of staying blank — including reading the
server's current revolver mode. Nothing changes in the moment; it just stops
the project from travelling empty.
* setEditorSetting mirrors into the project BEFORE its unchanged-value return.
Re-picking a value you already have is a no-op, so without this the settings
you never touch again — exactly the ones you'd lose — would never be recorded.
An ABSENT key still means "leave this browser alone": loading an old project must
never overwrite a configured setting with a default.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The one-line hints only covered the settings that already carried a wall of
text; roughly forty rows had nothing at all — Guidance, Decode, JoyAI, ALG,
DynaShift, the whole Continuity pane, and every Studio refiner field, none of
which even passed a hint through to field(). Each of those now has one
sentence in the same shape: what it does, what it costs, and what it needs to
be on before it does anything ("Nudges every step toward what your ratings
say you like. Costs 20-30% more time, and only does something with a
refinement key and enough liked generations to have learned a direction.").
The wording is drawn from the node tooltips in samplers.py / conditioning.py,
so the numbers in them are the measured ones, not invented: the 0.25-0.35
mid-scene band, the 45% mid-scene overhead, CFG 1.0 on distilled models, the
frame_overlap=0 + carry_i2v_guides warning.
Two rows also stopped dropping hints on the floor: renderStudioRefinerBool
never forwarded one, and Continuity's number fields had no way to take one.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Engine settings were unreadable: every experimental knob carried a paragraph
about cost figures, sampler caveats and failure modes, so nobody read any of
it. The hint is now ONE sentence saying what the setting does, and the
paragraph moves behind a collapsed "Details" link — still the only place some
of those numbers exist, just not in the way of the checkbox.
cut_opening_frames also learns MiniMax H3. It could never engage there: H3's
anchor is a keyframe condition row rather than a pinned latent prefix, so the
t2v guard read every H3 scene as anchorless, and its video latent sits on a
5k+2 grid an arbitrary cut cannot land on. On H3 the frames now come off the
head of the DECODED batch instead — exact to the frame — with the latent's
audio stream cropped by the same proportion of time, because audio decodes
from this node's LATENT output and would otherwise start N frames early. The
latent keeps its full video stream (off-grid the moment it is cut) and the
scene report says so.
Fixes a latent bug on the way: extra streams were cropped on dim 2 with the
video stream's frame count. On H3 dim 2 is the stereo channel and time is
last, so that would have cropped the audio to mono and left its duration
untouched.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The rule that a project with its own links/core_overrides keeps its
family was decided by looking at a real project on disk. That is the
wrong way to establish it: the finding is not repeatable, and reading
real project content is how that content ends up somewhere it must not
be. The case is now constructed — core_overrides on concat/audiodec,
which mean LTXVConcatAVLatent and LTXVAudioVAEDecode, both dropped from
H3's core — so the rule is verified without any user data.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every project on disk had model_family = None while the global default
said minimax_h3. The builder therefore read them as LTXAV, and LTXAV's
wiring routes audio_vae at LTXVAudioVAEDecode — a node H3's core does not
build. That is the reported "adds another Audio VAE output to a
non-existent node": one root cause, two symptoms.
Four things were wrong and all four are fixed:
* Creating a project seeded its pipeline config from the global default
ONLY when the global had slots or links. With no loader slots — the
normal state — the seed was skipped entirely and model_family,
full_control and core_overrides were all silently dropped. The global
IS the default for new projects, so copy it whole.
* /api/pipeline-deps reported the family of the GLOBAL config, so the
setup dialog showed "MiniMax H3" as already chosen for a project that
had no family at all. The user saw the right answer, closed the dialog,
and nothing was ever written. It now takes an optional ?pid= and
answers for that project.
* Saving a project's models wrote the body straight to the global
default, so any save that merely rearranged loaders erased the recorded
family. It is now preserved when the body omits it.
* PipelineCaps.modelFamily read state.models only. That is {slots: []}
while the live config loads and after a failed fetch — truthy, but with
no family — so an H3 project reported LTXAV for that window, taking the
frame grid, fps and the inert-setting chips with it.
Existing projects: one that has no pipeline of its own now inherits the
default family at read time (nothing is written behind the user's back).
One that HAS its own slots or links keeps its silence — verified against
the real project files, where the single project with core_overrides on
concat/audiodec is wired to LTXVConcatAVLatent and LTXVAudioVAEDecode and
would break if flipped. The dialog now shows those their true family so
it stays the user's call.
Finally, role targets and default wires are filtered against the family's
own core, so no role can offer or auto-wire a port on a node the graph
does not contain. That makes the phantom-node shape impossible rather
than merely fixed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The previous commit claimed the correction could not reach a stock
sampler. That was wrong. It can: the correction moves from the step to
the prediction, because a rectified-flow euler update is
x*r + denoised*(1-r), so scaling the audio part of (x - denoised) by the
same factor lands the audio on its own grid. A proxy denoiser wraps any
KSAMPLER-shaped SAMPLER and does exactly that.
Tying a model call to a step is the real constraint, and it is checked
rather than assumed: the proxy verifies on every call that the sigma it
was handed is this step's scheduled one, and switches itself off with a
console note the moment that stops holding. Samplers that evaluate twice
per step (heun, dpm_2, dpmpp_2s/sde, seeds_2) trip that on their first
corrector call and are left untouched.
Measured against comfy's real samplers with a perfect rectified-flow
predictor, so every number is pure schedule error (audio error as a % of
the stream's range, 4/8/20 steps):
euler 85/38/14 -> 0/0/0 exact at every step count
res_multistep 69/18/1 -> 21/23/15 helped at 4 steps, HURT at 20
dpmpp_2m 69/18/0.4 -> 20/22/14 same
heun, dpm_2 unchanged (proxy stands down)
The lesson worth keeping: this is a first-order integration error, so a
higher-order sampler already absorbs most of it once the schedule is
fine — which explains why res_multistep and er_sde behaved well on H3 at
20 steps while plain-euler paths did not. At 4 steps nothing absorbs it
(everything sits at 68-85%) and only the clock fixes it, which is the
regime turbo LoRAs put you in.
Not gated on any of this: the user picks the sampler, and the tooltip,
the Editor hint and a per-run console note carry the numbers so the
choice is informed. Also corrects two claims from the previous commit —
"no-op above 20 steps" (it is 14% on euler at 20) and "video is
bit-identical" (the streams share one attention sequence).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
MiniMax H3 denoises video and audio on two different flow schedules
(shift 12 / shift 3) but hands the sampler a single sigma grid, so the
DiT reconciles them by scaling the audio velocity by d(sigma_a)/d(sigma_v)
measured at the START of each step. Exact in the continuous limit; on a
few-step schedule the tangent badly overshoots the chord it stands in
for. On a 4-step shift-12 grid the final step drives the audio ~2.5x past
where its own schedule puts it, which is heard as distortion — the reason
the turbo-LoRA community ships a custom sampler for it.
h3_audio_clock on the Chain Sampler swaps that start-of-step slope for
the chord that actually spans the step, so audio lands on its own grid.
One scalar multiply per step: no extra model call, no extra memory. The
video stream is untouched. Above ~20 steps the two agree and it does
nothing, so it is aimed at turbo / distilled schedules.
Wired into the two FunPack sampler functions H3 can reach (Distilled
Flow, and Hybrid Euler 2S via its CONST rectified-flow path). A stock
ComfyUI sampler cannot be reached from outside its loop and says so
rather than looking active. Off by default, UNVALIDATED LIVE.
Editor: Settings > Engine > Experimental, plus the mirror-image inert
chip — H3-only settings left on against an LTX pipeline now warn beside
Generate the same way H3-dead settings already do.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
An image prompt comes back one tag long: comfy builds minimax_token_tags from
the tokenizer's INPUT sequence (embeds.shape[1]) while the conditioning is what
the encoder returned, and the vision block's expansion does not survive 1:1.
The reconcile treated any surplus as corruption and dropped the whole vector —
which on exactly those prompts discards the modality-0 marks for the image span
and leaves the DiT modulating the picture as if it were text.
Position i still means position i, so the tail is trimmed instead. Growing
(appended tokens) is unchanged.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two independent, silent breaks made an H3 i2v scene generate from the prompt
alone.
1. The Editor always runs split_by_transitions, and the per-scene conditionings
REPLACE the single entry Studio built — but they were re-encoded with no
reference_image and no h3_references. Both the frame-0 keyframe pin and the
Qwen vision presentation were gone before the sampler looked, even for a
one-scene project. The split now carries the anchor onto scene 0 and the
references onto every scene. Gated to H3: on LTX the anchor rides in the
latent, and adding Gemma3 vision to scene 0 would change existing output.
2. A MiniMax H3 Image to Video node hands its first/last frame pins out on its
CONDITIONING output, which had nowhere to go — the sampler's positive comes
from Studio. New optional h3_keyframes input on the Chain Sampler salvages
them: first frame onto the opening scene, last frame re-indexed onto the
closing scene's own final frame (the source node's frame_count is its own,
and a stale index would land mid-clip where PackedLayout refuses it). The
editor auto-wires it for H3 projects.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
mid_scene_guide_strength, the continuity pin/prior/mid-scene strengths, and
the per-guide strength field in the inspector were all clamped to 0.25-0.5 —
the measured audio-safe band, hardcoded as a limit. That band is advice, not
physics: it stays in the tooltips, the clamps go.
Backend clamps in timeline.py, the node input range, the JoyAI memory guide's
0.25 floor, and the three UI spinners all move to 0..1.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
v2a_grad_scale does nothing without joyai_audio_memory on any model, and
gating the chip on that toggle still left a warning about a knob the user
never asked about. Removes the chip, its fingerprint inputs, and the docs
line.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The gate added in f40ae82 was right, but fpActionbar never listed
joyai_audio_memory — so switching it off changed nothing the fingerprint
could see and the actionbar zone skipped its repaint, leaving the chip
stranded next to Generate until some other field forced a redraw.
Adds joyai_audio_memory, plus models.disable_core (every chip is gated on
the Chain Sampler running at all), and reads the models config through the
same state.models || project.models fallback PipelineCaps uses.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two things, both from the same debugging session.
v2a_grad_scale is JoyAI's coupling knob and is never installed without
joyai_audio_memory (see the call site), so a left-over value with the feature
off was being reported as an H3 limitation — blaming the model for a knob that
would be equally inert on LTX. Both the sampler's pre-run notice and the
Editor's inert-setting hint now gate on joyai_audio_memory.
The NaN guard only saw the sampler's OUTPUT, which cannot tell "the model
computed garbage" from "the model was handed garbage and propagated it" — and
those have disjoint suspect lists. _sample_chunk now checks the incoming latent
and the schedule first, and says which it was. The schedule check is there
because it is hand-typed and our solvers divide by sigma (er = sigma_next /
sigma): an interior zero or a repeated value is an instant Inf that poisons
every value downstream. A trailing zero stays legal — it is only ever a step's
target, never a divisor.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A chunk that samples to completion and returns NaN/Inf currently sails through
everything: the scene blend spreads it into the already-finished scenes, the VAE
decodes it, and the first thing that objects is ffmpeg's AAC encoder — "Input
contains (near) NaN/+-Inf", naming nothing that produced it, after the whole
montage has been paid for. The video half never objects at all: NaN through
astype(np.uint8) is undefined but silent, so it degrades with a warning.
_sample_chunk now checks the returned latent and raises with the run-phase label
("scene 2/3 · pass 1 of 2"), which stream, how many values and what dtype, plus
the causes worth testing in order — mismatched LoRA, a mis-inferred checkpoint
layout, a dtype the model cannot hold. It raises rather than warns because a
non-finite latent is unrecoverable and blending it forward destroys the scenes
that were still good. One isfinite() per chunk against a multi-second sample is
not a cost worth weighing; a latent the helper cannot read is ignored, never
fatal.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every 1500 ms the poll did `bodyEl.textContent = lines.join("\n")`, and
assigning textContent destroys and rebuilds the text node — which drops
whatever the user had selected. Copying three lines out of a running log was
a race against the next tick.
Now the write is withheld while a non-collapsed selection sits inside the log
body (a small "paused — selection held" note says so), and lands as soon as the
selection is released or moves out. Unchanged text no longer touches the DOM at
all, so an idle log is completely still. Adds an explicit ⏸ Pause toggle for
deliberate freezing, and Copy now copies just the selection when there is one.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
core_overrides are saved per core ID, not per node class, so switching model
family leaves the replaced node's input names sitting on its successor:
audiodec.audio_vae (LTXVAudioVAEDecode) survives onto VAEDecodeAudio, whose VAE
input is called `vae`. The Models panel lists the NEW node's inputs, so the
leftover is invisible there — it only surfaces when ComfyUI is handed a kwarg
the node never declared and the run dies inside it:
VAEDecodeAudio.execute() got an unexpected keyword argument 'audio_vae'
Guided wiring filtered these out as a side effect of its open-port check; full
control applied them.
The builder now refuses to write an input the node's class does not declare and
says so in the report (a leftover is not a reason to refuse the whole run), and
the family switch prunes overrides the new core has no input for, so it stops
being carried in the saved project. A valid override is untouched by both.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A SAMPLER object is only the step function, so picking KSampler used to leave
the pass with no schedule at all unless a sigma list was typed by hand.
steps + schedule now build one from the loaded model's model_sampling, exactly
as ComfyUI's BasicScheduler does. The schedule dropdown is the switch between
the two sources rather than a silent precedence rule: "use_user_sigmas" — the
default, so nothing changes for an existing config — runs the Sigmas field as
typed, and any real scheduler computes instead, leaving the typed schedule
parked in the field to switch back to without retyping it. Studio logs the fact
that the field is being ignored, and steps is only shown while it applies.
Both frontends, same change.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
minimax_h3.py shipped tags_match / token_tags_length / extend_token_tags with a
docstring saying a mismatch is a hard IndexError mid-forward — and nothing ever
called them. Studio edits conditioning in many places, and H3 tokenizes reference
images into the same span (ref_image_size "match" scales them to the output
resolution), so the token count moves with the prompt AND with the resolution.
That is why the crash looked intermittent and why editing either one made it come
and go, reported only as a bare "list index out of range" at step 0.
Studio now reconciles before returning: extend the tags when tokens were appended,
drop them when tokens were removed (absent tags the DiT handles, wrong tags it does
not), and print which happened — losing the tags changes how the text span is
modulated, so it must not be silent.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Studio's KSampler mode passes the chosen name straight to comfy.samplers.
sampler_object(), so any sampler this ComfyUI has always worked — but both
frontends offered a hardcoded list of eight written before the rest existed.
That silently hid 36 of them, including res_multistep, which is what ComfyUI's
own MiniMax H3 templates use.
Both now read sampler_name from the live KSampler node spec and fall back to the
old list if that fetch fails. A saved name missing from the live list stays
selectable instead of being swapped for the first option.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ComfyUI's official H3 templates (video_minimax_h3_r2v / _i2v) feed the raw sampler
latent to BOTH VAEDecode and VAEDecodeAudio and let each VAE take its own stream.
We unbound it first via LTXVSeparateAVLatent, so vae_decode_audio — which hands
samples["samples"] straight to vae.decode() — received a different object than the
reference graph gives it. Video was unaffected because the Chain Sampler decodes
video itself, which matches the symptom: clean video, corrupted audio.
Separate stays in the H3 graph for the refinement save's video latent. LTX is
untouched. UNVALIDATED live — no H3 hardware here; revert this link if audio is
unchanged.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Replaces the Engine Settings 'Reference media' tab. Any image / video / audio in
the Media Bin (Editor) or Gallery (Easy Gen) takes an 'R' mark beside the identity
pin; any number can carry one, and mark ORDER is the numbering the badge shows.
Each marked item then appears as an input source in Models & Pipeline — 'R1 ·
face.png (image)' — for every socket its kind can fill, so references reach a node
the same way the timeline image does.
At generation the marked files are copied into ComfyUI's input folder and the
builder injects one loader per reference, chosen by what the DESTINATION socket
asks for (IMAGE / AUDIO / VIDEO), shared when two sockets take the same one. A
reference whose file is gone, or one no installed node can load into that socket,
is reported against the socket that wanted it instead of silently unwired.
The editor no longer feeds Studio's built-in ref2va list: reference media is
wired now, not configured. Project.h3_references stays readable so old project
files still load, but nothing writes or sends it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
git pull --ff-only cannot cross a rewritten history — every local commit has a new
hash, so git reports divergence and the in-app update dead-ends in a git hint.
On that failure the updater now asks git cherry whether any local commit's CONTENT
is missing upstream: if none is, nothing can be lost, so it realigns to origin and
reports that it did; if there is real local work, it refuses and says how many
commits would go. A cherry that fails to run counts as 'not proven safe'.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ComfyUI expands an autogrow list into sockets named <parent>.<template name> —
'ref_images.ref_image_0' — and splits that path to rebuild the node's list. We
sent the bare template name, which validation reports as a missing required
input and, where it slips past, arrives at execute() as an unexpected keyword
argument. Verified both ways against a live ComfyUI: the bare name is rejected,
the dotted one queues and executes.
The UI keeps showing the short name; only the wire key changed. Configs saved
under the old bare name are renamed in place on load, and the builder maps any
that remain, so an existing pipeline doesn't keep failing every run.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The editor seeded its fixed core nodes from a saved graph dump. Replaced with a
small file holding just the widget values the builder consumes — no free text,
nothing user-typed, since every text field a run needs arrives from the project
at generate time. Graph output verified identical against live object_info.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three things blocked the MiniMax-H3 reference node in Models & Pipeline:
Autogrow list inputs (COMFY_AUTOGROW_V3) were offered as a single unwireable
socket typed after the list wrapper, so nothing — not the timeline image, not
another node's IMAGE output — matched them. They are a TEMPLATE, not a socket:
ComfyUI expands them into ref_image0, ref_image1, … and rebuilds the dict from
whichever indices the API graph carries. connection_inputs() now expands them
the same way, typed as the element type, and the editor shows the filled entries
plus one empty slot so a 50-entry list doesn't bury the node. Auto-wire skips
them — a lone IMAGE producer would otherwise be copied into every index.
A node's own prompt field can now be driven by the project prompt: the linked-
input 'Driven by' list gained Prompt / Negative prompt / Seed, filtered to the
widget kinds each can legitimately fill.
A combo value whose file isn't on this machine (a saved ArcFace projector with
models/loras empty) made ComfyUI reject the WHOLE prompt, so nothing queued even
with Best-FaceID switched off. Editor-supplied Studio/Chain-Sampler values are
now checked against the live list and fall back to the node's default with a
visible note, the same rule slot nodes already had.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every one of them was a real test of a live feature. 18 failed only in a full-suite run and
passed alone, 4 asserted behaviour that was deliberately changed and never got updated.
Ordering (18): each test module built its own partial `comfy` stub and registered it with
`sys.modules.setdefault(...)`, so the first module to import won and every later module's
richer stub was silently discarded — the scene chain sampler then died on `comfy` having no
`.sample`, and the mask-safe attention install bailed at its first guard because
`import comfy.ldm.modules.attention` resolves through the PARENT's attribute, which one
module set and another did not. tests/_comfy_stubs.py now builds that package tree once,
wiring every parent attribute, and test modules attach their fakes as attributes on the
shared modules (per-test, via fixtures, where the fake is only theirs — the idiom
test_detailing.py already used). Same file also stubs `server.PromptServer`, whose absence
made every module that imports templates.py (i.e. anything importing conditioning) fail on
`No module named 'server'`.
Stale assertions (4):
* the "every learning rating" loop excluded non-learning ratings by hardcoded LABEL, so the
later-added `Wrong appearance|loved` and the two control sentinels were asserted to train
phrase memory. Now filtered by profile key, with the reason per key.
* three split_by_transitions tests still used "scene ten" / "cut to" as cut markers. The
unified splitter cuts on a real transition trigger or the generic `scene <N>` label, on
purpose — an incidental phrase must never split a prompt. Rewritten on the current
contract, and they now also pin that the delimiter is dropped rather than encoded.
Verified: 584 pass, and every file passes alone, in reverse order, and with movie_editor's
suite standalone. Mutation-checked the rewritten split tests (disabling the scene-label
pattern fails 4 of 5) so they are not passing vacuously.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
H3 support was written against ComfyUI's PR; it has since merged (57500fc5, shipped in
v0.30.0) and the weights are out. Everything the adapter assumed still holds — audio time
axis, 17k+5 grid, batch-1 forward, the imported optimized_attention symbol, every payload
key — so this is what the release itself changed, not a correction of the reading. LTX-2 /
LTXAV is untouched throughout: a project picks a family and both are first-class.
* i2v anchors now work on H3. LTX writes the image into the starting latent
(LTXVImgToVideoInplace); H3 has no such path — its anchor is a condition row packed
beside the text — so a scene's anchor image becomes a frame-0 keyframe pin instead, and
the LTX node is no longer run against an H3 latent. Studio hands the source image's
pixels to the sampler (it owns the CLIP, the sampler owns the VAE). Pins now MERGE, so
a first-frame anchor and a last-frame guide coexist: H3's own first-and-last mode.
* Frame geometry follows the model: frames per scene snap to the family's grid (LTX 8k+1,
H3 17k+5) in both frontends, in the graph builder, and in the sampler, and H3 renders at
its fixed 24 fps. Picking a family moves the project onto its geometry and says so.
* The two checkpoints are named. fl2va (anchors) and ref2va (references) load identically
and never reject each other's conditioning, so the run reports which mode it is in and
the setup panel spells out the split.
* Pipeline setup is no longer a placeholder: released, with the published filenames.
* Reference images can be encoded at the reference pipeline's 2048px short edge.
Fixes found on the way:
* A video reference lost its soundtrack between Studio and the sampler — Studio had
already announced it to the text encoder as <Audio 1>, so every later audio ordinal in
the prompt pointed one reference earlier than written.
* Easy Gen never knew it was on H3: its model config lives on the project, which the
shared capability check did not read, so every H3 warning silently reported LTX.
* An off-grid scene length failed with arithmetic instead of an explanation.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Setting the family in Models was the wrong first step — the family decides which nodes
and which model files everything after it looks for, so it is now the first question in
the Pipeline setup panel rather than something to find afterwards.
MiniMax H3 is not released: its nodes are in an unmerged ComfyUI PR and no weights exist.
Rather than hide the family until then, pipeline_deps.FAMILY_SETUP declares what it will
need — the slot nodes (with why each one is there) and the model files with their target
models/ folders — and the panel shows that as "not in your ComfyUI yet" / "to download
when published", with a link to the PR. Picking it configures the project now; it starts
working when ComfyUI ships the nodes and the weights land, with no further FunPack update.
The table is deliberately data, not code: shipping the real thing means flipping
"released" and filling in the hints, not editing the setup flow.
Dependency detection is family-aware throughout, so an H3 project is no longer told to
install packs for LTXVConditioning / Concat / LTXVAudioVAEDecode — nodes its graph never
emits. An unreleased family still counts as needs_setup, or the modal would never open
and a project that cannot generate would look ready. The install button and its lead text
only appear when there is something Manager can actually install; offering to fetch an
unreleased model would be a promise the button cannot keep.
Also reachable on demand: Models → Model family → Setup…, since the auto-prompt is
one-shot per browser and was previously unreachable once dismissed.
Verified live: picking MiniMax H3 in the panel switches the project, lists the H3 node and
all four model files with their folders, links the PR, and switches back cleanly.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The graph builder is now parameterized by model family instead of assuming LTXAV. H3
keeps the core's shape but three of its nodes do not apply: LTXVConditioning (stamps LTX's
frame_rate onto conditioning, which H3 has no field for), LTXVConcatAVLatent (H3's own
EmptyMiniMaxH3LatentAV emits both streams, so the latent feeds the sampler directly) and
LTXVAudioVAEDecode (reads an output_sample_rate H3's audio VAE does not define — it would
raise). Requirements, wirable ports, role targets, guided-wiring hints and the port picker
all follow the family, so a loader can no longer be wired into a node the graph never emits.
The family is a Models setting, asked and never guessed from a checkpoint filename: a wrong
guess fails deep inside ComfyUI at generate time instead of in the panel that can fix it.
Video references now work, rather than being refused. FunPack owns the pipeline and already
requires ffmpeg for preview/export, so minimax_h3 decodes reference clips itself — once, at
24 fps — and derives both views from that single decode: the 2 fps timestamped frames Qwen
sees, and the full-rate latent block the DiT packs. A clip's own soundtrack rides with it,
presented as its <Audio j> label immediately before its <Video k>, which is what ties the
two together. Capped at 15s because reference tokens ride every sampling step.
Editor UI: a Reference media view (H3 only — there is no LTX equivalent to grey out) with
per-type 1-based tags shown exactly as the prompt must spell them, reorder controls that
renumber both sides at once, and a soundtrack picker on video entries. Warning chips beside
Generate for the LTX-only sampler settings H3 switches off, so that is known before a
generation is spent rather than after; Easy Gen gets the same warnings on its strip and the
same family selector (it already loads models.js and pipeline_caps.js).
Verified live in the Cutting Room: the family toggle flips requirements, ports and hints
both ways and persists per project; tags renumber on reorder; the H3 chips replace the
Best-FaceID chip on H3 and hand back to it on LTXAV.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
H3 packs reference blocks into the sequence itself (own position grid, never denoised,
never rendered), which is the thing Best-FaceID approximated on LTX with overlap tokens
plus a trained ArcFace projector. On H3 there is nothing to project and nothing to slice
back off, so this is the model's own feature, not a port of that one.
A reference has to reach the model twice and in the same order both times: as
presentation (Qwen sees "<Picture 1>: <vision block>" so the prompt can name it) and as
a packed latent block. Those encodes happen in different nodes, so the ordered spec
travels between them:
- Studio owns the CLIP: resolves the reference list, bakes the presentation via the
tokenizer's minimax_ref_items, and records what it presented as funpack_h3_refs on
the conditioning. Reference media and a first-frame anchor are separate conditioning
modes, so source_image steps aside (loudly) when references are present.
- Chain Sampler owns the VAE: encodes exactly that list, in that order, sized against
the scene's canvas, and attaches minimax_refs. Applied on every scene branch so an
identity holds across the whole chain, and cached per run so it is encoded once
rather than once per scene.
Order is the contract — drop or reorder an entry and "<Picture 2>" points at a
different reference than the text encoder saw. Anything that cannot be encoded is
dropped AND named, and an audio reference lost for want of an audio_vae says out loud
that later <Audio j> labels have shifted.
New optional audio_vae input on the Chain Sampler (audio references only; image
references need just the main vae). Video references are refused with a reason rather
than half-implemented — they need 2 fps frame sampling and a video loader FunPack does
not own.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Day-0 groundwork for Comfy-Org/ComfyUI#15224 (MiniMax H3). New minimax_h3.py is the
one place that knows how H3 differs from LTXAV; everything else consults it.
Four divergences that failed silently before:
- Audio latent axis. LTXAV audio is [B,C,T,freq] (time on dim 2, same as video);
H3 audio is [B,32,stereo,T]. Both 4-D, so only the model family can say which
axis is time. The Chain Sampler now carries a per-stream time axis through
continuation, blending, tail crops and JoyAI audio memory.
- Frame grid. LTXAV 8k+1 -> k+1; H3 17k+5 -> 5k+2. H3's downscale_index_formula
is the index map, not the count map, so the old uniform formula under-counted
by ~20%. Frame counts now come from vae.downscale_ratio[0], which reproduces
LTXAV's answer exactly.
- Attention patches. LTX calls optimized_attention through its module; H3 binds
it at import, so FunPack's temperature/capture patch was inert on H3. It now
rebinds every module holding a reference, and position-indexed work (BachVid
K/V, attn importance) sits out H3's pre-reshaped [1,heads,S,D] layout.
- Batch size 1. H3's DiT refuses a batched forward and comfy batches cond+uncond
for cfg != 1.0. install_batch_split runs them one at a time, keeping cfg live.
Guides become keyframe pins: H3 packs condition rows into the sequence instead of
appending a masked latent frame, so no tail is appended and none is cropped. The
layout only accepts a first/last pin, and a mid-clip request is refused up front
instead of raising inside PackedLayout.
Studio: reference images route through Qwen3-VL's images= kwarg (the Gemma3 vision
probe returns False for H3 and was dropping them); the attn2 K/V direction patch
reports that H3 has no cross-attention to hook instead of installing a no-op.
Features that depend on LTX transformer structure (bounded attention, Best-FaceID
identity transfer, v2a_grad_scale, segmented detailing, second-pass latent ops) are
now switched off explicitly on H3 with the reason printed, rather than each one
discovering its own missing attribute mid-scene.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
"Generating (44s)", counted from the press until the finished video comes
back. Both frontends.
Editor: the clock spans the WHOLE operation, not one run. pollStart already
existed but restarts on every run of a multi-run montage, so a press that
kicks off three runs would have shown the third run's age. genWallStart is
wrapped around the public entry points (generate / generateMontage /
generateSelected) in a finally, which no early-return error path can miss.
The tick writes the button's textContent directly and never re-renders the
zone: rebuilding every button once a second would throw away focus and hover
for a number one label owns. For the same reason the elapsed value is NOT in
store state and NOT in fpActionbar — the zone still repaints on gen.state,
which is what starts and stops the ticker.
A run started from Selected counts on that button; a montage or a single
scene fired from the inspector counts on the main one. Re-attaching after a
reload reads "1m 04s+" with a tooltip saying it counts from the reconnect,
since the press happened before the page load and the real total is unknown.
Seconds are zero-padded so the label's width changes once a minute rather
than once a second; tabular figures keep it from twitching in between.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
_downscale_to used mode="area" — a 2x2 box for the exact-2x ratio both
callers hit. A box loses on both axes at once: its passband droops well
before Nyquist (detail comes back soft) and its stopband leaks (the detail
the upsampler just invented folds back as aliasing). Soft AND speckled is
precisely the reported "sharpen leaves smearing and artifacts".
Antialiased bicubic instead. On a full-band chirp, RMS error against an
ideal brick-wall decimation drops from 0.316 to 0.244 (~23% closer).
Constant input still round-trips exactly, so the DC behaviour both callers
rely on is unchanged.
This is shared with segmented detailing's 'repair' paste, which downscales
its refined crop through the same helper — that path gets the same change.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Best-FaceID reads identity from an identity_pin-tagged guide, and only the AUTO
continuity builder ever applies that tag (timeline._identity_pin_guide). Every
way of losing it is silent: the run succeeds and simply carries no identity.
Three such states, all verified against _run_sampler_inputs (tagged guides in
the emitted funpack_scene_guides payload: 2 when healthy, 0 in each bad case):
- no identity pin set at all
- custom guide stack on -- manual guides REPLACE auto guides (`manual or auto`),
and manual GuideEntry has no identity_pin field, so the pin is dropped
- auto continuity off -- same outcome by the other route
Shown on the main window of both frontends, not inside the settings pane that
turns it on: you would only see it there if you already went looking.
- shared predicate: PipelineCaps.identityTransferIssue -> {short, detail}
- Editor: amber chip beside Generate in the timeline header, click opens
Engine settings; full text in the tooltip
- Easy Gen: full-width amber strip between the preview and the Generate row
fpActionbar had to grow the identity fields -- it hashed only pid/sel/gen, so
setting the pin would have left a stale warning sitting next to Generate.
Verified live in both UIs across all five transitions (no pin -> pin -> stack on
-> stack off -> auto off -> feature off); appears and clears correctly in each.
Silent when the Chain Sampler is not in the pipeline, where the knob is inert.
Tests: 22 failed / 465 passed, unchanged from baseline.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Restores source_needs_anchor_media (removed as unreferenced in 0b68af5) and
wires it up, which is what it always lacked.
The existing check, _missing_scene_anchor_media, catches a media_ref that IS
set but has fallen out of the media bin. Nothing caught the ref never being
set: scene_anchor_media_refs guards on `if ref and stype in (...)`, so such a
scene is skipped silently and renders with no anchor at all, quietly ignoring
its own Source setting.
Warning, not a block -- the run is valid, it just may not be the shot the user
set up. Surfaces in two places:
- backend: report["unsatisfied"] on the generate response
- editor: an insp-hint warn under the anchor picker, where Source is chosen
Frontend mirrors the predicate in pipeline_caps.js; the two must stay in step.
Also drops a comment left stranded by 0b68af5's constant removal.
Verified live in the Movie Editor (ComfyUI on :8188): warning appears for
image/mixed with no ref, disappears once an anchor is picked, and never shows
for carry. Silent without Chain Sampler, where an anchorless image scene
degrades to t2v by design.
Tests: 22 failed / 465 passed (baseline 22/460 -- +5 passing, 0 new failures).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
7106db0 added scene_anchor_media_refs by writing it over the "@dataclass /
class GuideEntry:" header lines. The class body survived as unreachable code
after that function's return, so GuideEntry was referenced in three places but
never defined.
Effect: every code path that switches the per-scene i2v guide stack on raised
NameError -- build_scene_guides_payload and continuity_media_refs both died as
soon as guide_settings.stack_enabled was true. It went unnoticed for seven
weeks because the stack defaults off and no test covered it.
Fix moves the stranded body back out of scene_anchor_media_refs and restores
the two lost header lines. The body is byte-identical to b9f6e74; only its
position and header changed. scene_anchor_media_refs is untouched.
Adds two regression tests, both verified to fail against the unfixed file.
Tests: 22 failed / 462 passed (baseline 22/460 -- +2 passing, 0 new failures).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
json_safe and maybe_store_string were reachable only through
collect_template_payload (removed in 055b256). Caught by re-running the
orphan scan after that commit rather than assuming the first pass was final.
Checked: 1 occurrence each repo-wide (grep -a), 0 refs in ComfyUI core/FunUI.
Tests: 22 failed / 460 passed, identical to baseline.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
_leading_trigger_re plus its _TRIGGER_RE / _TRIGGER_RE_BUILT module globals.
The globals were touched only inside the function itself, so the whole
memoisation block was self-contained and unreachable.
Checked: 1 occurrence repo-wide (grep -a), not a route handler, not in
NODE_CLASS_MAPPINGS, not an INPUT_TYPES widget, 0 refs in ComfyUI core/FunUI.
Tests: 22 failed / 460 passed, identical to baseline.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
source_needs_anchor_media, is_chain_only_source_type (+ its now-orphaned
_CHAIN_ONLY_SOURCES constant), _job_snapshot, default_slot_extras,
_ui_output_links.
Kept deliberately: DEFAULT_WIRES_BY_ROLE and DEFAULT_INPUT_SOURCES_BY_ROLE
survive default_slot_extras — both are still exported via the slot-defaults
payload at pipeline_wiring.py:235-236.
Checked: 1 occurrence each repo-wide (grep -a), not route handlers, not in
NODE_CLASS_MAPPINGS, not INPUT_TYPES widgets, 0 refs in ComfyUI core/FunUI.
Tests: 22 failed / 460 passed, identical to baseline.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
collect_template_payload and template_field_summary. Both from the original
template-manager commit; the surviving /funpack/templates routes build their
responses directly and never call either.
Checked: 1 occurrence each repo-wide (grep -a), not route handlers, not in
NODE_CLASS_MAPPINGS, not INPUT_TYPES widgets, 0 refs in ComfyUI core/FunUI.
Tests: 22 failed / 460 passed, identical to baseline.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>