Adversarial review of the previous commit found four real defects, all
confirmed:
1. A run covering more than one scene (the whole-timeline H3 shape, or any
single-scene click whose gen-unit has sibling subclips) only ever sent
the FIRST scene id as extra_data.funpack_scene_id. On reload mid-run,
reattach recovered just that one id, so _recordSegment only ever wrote
the finished media onto scene 1 -- every other scene the job actually
rendered silently got nothing recorded, no error shown.
Fixed by adding funpack_scene_ids (plural) alongside the existing
singular field, threaded through client.js's queuedFor and session.js's
reattach() (both additive -- a new optional third onAdopt argument and
a new optional response field, ignored by every existing caller such as
app/boot.js) down to generate_bridge.js/store.js, which now record onto
every adopted scene.
2 & 3. A run adopted on reload never primed pollStart/genWallStart, so
_elapsed() and the transport button's own clock read time since the
Unix epoch instead of real elapsed time, and never stopped afterward --
wedging genElapsed() for the rest of the session (its own guard is "if
already started, do nothing"). Fixed: the "adopt" handler now primes
both clocks (flagged approximate, same as the deleted
resumeRunningGeneration used to), and every terminal run phase now
stops them.
4. check()'s `notes` field (e.g. "N settings didn't reach the pipeline")
was fired through transport.warn() but nothing ever subscribed to
GenerateBridge's "warn" event -- silently dropped, reproducing the
exact bug class the deleted code's own comment said must not recur.
Fixed: wired into state.notice, same as the old code did.
New tests added for both the plural-scene-id path (client.test.js,
session.test.js) and the fallback-to-one-scene case. 691/692 JS tests and
548/548 Python tests pass (the one JS failure is the pre-existing,
unrelated projects.test.js import-race flake). Live-verified against the
dev server: a multi-scene GenerateBridge.generate() call still correctly
surfaces a real pipeline refusal with no console errors.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Two routes silently shared /api/log; a pack's "updates available" badge
outlived the update; nothing server-side stopped a restart mid-generation,
and the fixes for that had their own dead end, a lock scoped to the wrong
event loop, an unlocked status fetch, and a check that only ran once
instead of at the moment it mattered. A run's result was lost outright if
the project it belonged to wasn't the one open when it finished -- on a
project switch or a page reload. A brand-new project inherited the
previous one's width, then inherited the wrong "default" for it.
Found and confirmed via an adversarial pass over 9dcd94c..HEAD, one round
per fix.
/queue answers with queue_running AND queue_pending, and only the first was
looked at. A job sits in the second from the moment /prompt returns until the
worker picks it up -- always briefly, and for as long as it takes whenever
something is running ahead of it, which on a shared box is the ordinary case. A
reload in that window found nothing, handed Generate back, and let the same job
be queued twice: the first copy then ran unnamed, its messages failing the id
check, burning GPU time while the UI narrated the second.
Every fixture in client.test.js was cleaner than the real payload -- none of
them carried a queue_pending key at all, though ComfyUI always sends both. That
is the third time a stand-in tidier than the real thing hid what it stood in
for, so the fixtures now carry both halves, including the malformed entries a
real answer can contain.
Also renamed, because the function no longer answers the question its name
asked: queuedFor returns which half it found the job in, and a job waiting its
turn is adopted as QUEUED. Saying "working" over a run that has not started is
the same small lie as saying "cancelled" over one that has not stopped.
The fix a commit ago buffered messages only while QUEUED, which is the window a
page creates for ITS OWN run. Reattach opens a second window nobody covered: the
socket is live, the phase is idle, and the queue has not yet said which run
belongs here. Anything arriving in that gap fell straight through and was
dropped -- and a run that ended inside it had already left `queue_running` by
the time it was asked about, so nothing found it either. The GPU produced a
picture, the app said Ready, and the result was gone.
Held whenever the id is unknown, not only while queueing. Then two questions on
load rather than one: what is still running, and -- if nothing is -- which of
the runs this page actually SAW on its own socket was ours, answered from
ComfyUI's history by the client id it recorded. Only ids seen during this page
load are asked about, so a result from an hour ago cannot be resurrected as
though it had just arrived.
Every reattach test called adopt() before sending anything, so none of them was
ever in the window that adopt() exists to close.
Three faults, all living in the length of the /prompt round trip -- a real
network delay during which the socket is already live and everything ComfyUI
broadcasts arrives.
* Cancel in that window POSTed `{"prompt_id": null}`, and ComfyUI reads a
missing prompt_id as a GLOBAL interrupt: it stops whatever any client is
running. On a shared box that is someone else's generation, and the clicking
user's own request may not even be queued yet. The cancel is remembered and
sent once the run has a name.
* `mine()` treated "no id yet" as "everything is mine", so a stranger's
execution_success and images flipped this run to Done -- showing another
client's picture, for a run that had not started. Messages arriving before the
id are now held and sorted out once it is known; dropping them instead would
lose a run short enough to finish inside the round trip. The buffer is bounded,
because on a shared server it fills with other people's traffic.
* The socket only opened at Generate, so the reattach the client id exists for
never happened: after a reload the app sat at Ready while its own generation
ran on, and pressing Generate queued a SECOND job beside it. It listens at
load and asks ComfyUI's own queue which run belongs to this client -- asked
rather than remembered, because a remembered id reattaches to a run that may
have finished while the page was gone.
Every existing test awaited start() before sending a message, so none of them
ever entered this window. The new ones hold the /prompt response open, which is
what a round trip actually is.