httpx logs every queue-status poll at INFO, and fal is polled about six
times a second, so a two-minute call writes ~730 identical lines - half of
a whole session log. A real error ends up scrolled off the screen.
The polling is how queued endpoints work and is untouched. Only the line
for a poll that answered 202 (still queued) is dropped; the 202 -> 200
transition, the GET that fetches the result, the POST that started the
call, and any failing status all still get logged.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The guard formatted self.ENDPOINT into its message, but only the GPT Image
node defines that attribute. On the other three callers the lookup raised
AttributeError from inside the guard, so a warning that was meant to be
non-fatal killed the run instead - the Z-Image skin pass returning fewer
pixels than it asked for is expected there, and Resize To Match already
puts it back.
The endpoint is now a parameter each caller passes, with getattr and the
class name as fallbacks so a caller that forgets still gets a name rather
than a crash.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
openai/gpt-image-2.5/sunburst/edit was never added to QUEUED_ENDPOINTS
when the node was introduced, so it took the synchronous client.run()
path and held a single HTTP connection open for the whole generation. At
quality "high" that is 70-80s and survives; at "max" it does not, and
fal's edge closes the connection: "Server disconnected without sending a
response". Ten runs at max failed on the first one.
"server disconnected" was also missing from TRANSIENT_MARKERS, so the
call did not retry either - a transport failure was treated as a bad
request. Added it along with the other shapes the same condition takes
(connection aborted, remote end closed connection, IncompleteRead).
The queued path polls with a 20-minute client timeout, which is what the
sibling gpt-image-2 endpoint has always used.
Verified without spending a call: the endpoint now routes to the queued
branch while grok correctly does not, and the exact error string from the
failed run classifies as transient while a validation error and a 401
still do not.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The guard as first written raised whenever the returned area fell below
half the request. That would have broken a working graph: the Z-Image
skin pass deliberately generates below the source resolution and has
Resize To Match put it back afterwards, so a shortfall there is the
design, not a fault.
Only the aspect-ratio check stays fatal - a changed aspect means the
picture was reframed instead of resized, and no caller wants that. A
short return now logs a warning naming the likely cause, which is enough
to make the 736x896 collapse visible without turning a tolerated
condition into a failure.
Checked against the graphs in use: the two inpainting workflows drive
sunburst through "match input (original)", which resolves to auto and is
never guarded; z-image sends dimensions it has already clamped itself, so
request and response match and the path stays silent. The warning fires
only when fal returns something other than what was asked for.
Every touched module imported against a real ComfyUI path, and the guard
re-tested across the auto, exact, clamped, collapsed and reframed cases.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The returned-size check added for GPT Image 2 moves to
ImageProcessingMixin and is wired into the other nodes that request
explicit dimensions: Seedream V4.5 and V5 Pro in custom size_mode, and
Z-Image Inpaint+LoRA when match_input_resolution builds a width/height.
Nodes whose size resolves to a preset string are left unchecked, because
there is no requested size to hold the API to.
Z-Image carried the same false claim GPT Image 2 did: the image_size
tooltip said "auto" keeps the input image's own size, while the tooltip
on match_input_resolution three fields below said fal's auto and enum
presets land around a 512 px short side. The second one is right. The
default path was never affected - match_input_resolution defaults to
true and bypasses the preset - but anyone turning it off was reading a
promise the endpoint does not make.
Seedream's image_size had no tooltip at all, so auto_2K and square_hd
looked interchangeable; it now says which presets keep the input's
aspect and which impose their own.
Surveyed and left alone: Grok v2 edit already guarantees the size by
resizing to image_1 locally after the call; Nano Banana Pro/v2 and Wan
2.5 pass an aspect and resolution through without claiming to preserve
anything. One gap worth noting rather than changing silently - Grok
Quality edit has no output_size / match-input option, so unlike its v2
sibling it cannot be made crop-stitch safe.
Guard re-tested from its new home, including image_size arriving as a
preset string or None, and every caller verified to inherit the mixin.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every fal call now goes through the cost ledger in call_api, so all 37
fal-backed nodes are tracked with no per-node change:
- modules/fal_pricing.py: price table for the 35 endpoints this package
calls, read from fal's own catalogue on 2026-09-09, plus an estimator
per endpoint (output count, resolution/quality, megapixels, video
seconds, training steps). Endpoints fal bills per GPU-second or per
token publish no per-call price, so they are marked unpriced with a
reason instead of being guessed at.
- modules/fal_cost_ledger.py: in-memory record of every call and its
cost, plus report formatting.
- Superside Fal Cost Report node: breakdown + total for 'this run' or
the whole session, with after_image/after_text inputs to force it to
run last.
- Each node's fal price is appended to its DESCRIPTION at registration,
so it shows in the node's tooltip.
- `python -m modules.fal_pricing --check` diffs the recorded prices
against fal's live catalogue and flags drift or renamed endpoint ids.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
fal's gpt-image-2 (and other queued endpoints) intermittently return 504
Gateway Timeout / "downstream service unavailable" when the upstream provider
is briefly down - a server-side hiccup, not a bad request. In batch pipelines
this aborts the whole run.
call_api now retries transient errors (502/503/504, downstream unavailable,
gateway/read timeouts, connection reset) with a 5s/15s/30s backoff, while
non-transient errors (validation, auth) still fail immediately without retry.
Applies to every Superside fal node via the shared APIClientMixin.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
get_client() now falls back to the FAL_KEY environment variable only when
the api_key input is left blank; an explicit pasted key still wins and
behaves exactly as before. This lets automated/headless deployments (e.g.
el-workflow's Replicate pipeline) supply the key via a redacted env var
instead of embedding it as literal text in the workflow JSON, where it
leaks into request logs. Manual GUI usage is unchanged (the fallback never
engages when a key is present). Applies to every node via the shared base
class. Docstring + README updated, including the note to keep requiring an
explicit key on shared multi-tenant backends.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
A manual counterpart to Smart Detail Sheet with an interactive in-node
widget: a row of numbered on/off buttons above the image toggles up to 6
boxes; each active box is drawn on the image where it can be dragged to
move and scrolled to resize. Boxes are true 1:1 squares (in image pixels),
enforced both in the widget and again server-side. On run, active boxes are
cropped, upscaled locally (Lanczos), and composited alongside the original
using the same aspect-adaptive layout as the Smart node. No AI, no API key.
Shared crop/compose logic (blank-crop discard, fixed-size square cropping,
aspect-adaptive composition, original-stays-dominant sizing) is extracted
from smart_detail_sheet_node.py into a DetailSheetCompositionMixin in
base_node.py, which both nodes now use.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Both video nodes (Gemini Omni Flash Edit, SeedVR Upscale Video) now
accept a VIDEO input (connect a LoadVideo node directly) and return a
VIDEO output (connect straight to SaveVideo/PreviewVideo), in addition
to the raw video_url string for anyone who wants the direct fal.ai
link. Uploading/downloading happens internally via the new
VideoProcessingMixin (comfy_api.latest.InputImpl.VideoFromFile) - no
more manual URL copying between nodes.
BREAKING CHANGE for existing workflows using the old `video_url`
STRING input on these two nodes - reconnect them to a LoadVideo node.
Verified against the real API end to end: uploaded a local test video,
ran a real Gemini Omni Flash edit, downloaded and re-wrapped the result
as a native VIDEO object, and confirmed it saves to disk correctly via
save_to() (the same call a SaveVideo node would make).
New node wrapping google/gemini-omni-flash/edit: takes a video URL and
a simple text instruction (e.g. "Make this video anime. Keep everything
else the same.") and returns the edited video's URL. Added to the
queued-execution endpoint list since video editing commonly takes a
while and a single long-held connection is fragile for that.
Verified against the real API end to end: empty video_url correctly
rejected, and a real edit completed successfully.
These endpoints commonly take several minutes. A single blocking HTTP
request (client.run()) held open that whole time is fragile - two
production runs both failed after ~11 minutes with "Server disconnected
without sending a response." Switched to fal_client's official
subscribe() helper (submit + poll + fetch in one call) instead of a
hand-rolled submit/iter_events/get loop, which was redundant with
subscribe()'s own internal polling. Added an explicit 20-minute
client_timeout - subscribe() waits indefinitely by default, which would
otherwise hang a node forever on a genuinely stuck job instead of
failing with a clear error.
Verified against the real API: openai/gpt-image-2/edit (already using
the queued path) completed successfully end-to-end through subscribe().
Ported and curated from the internal fal-flux-nodes package: dropped
3 redundant nodes (Nano Banana v1, AnyVLM, SeedVR v1) that were fully
superseded by newer variants already in the set. Every node now takes
an explicit api_key STRING input instead of reading from a config file
or environment variable, so no key is ever baked into this repo.