From fb097bedc2af8cc7499fba8ab6da8811ecc40491 Mon Sep 17 00:00:00 2001 From: Matt Miller Date: Tue, 12 May 2026 11:06:28 -0700 Subject: [PATCH 1/4] Mark deprecated cloud-runtime endpoints in spec (#13789) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Mark deprecated cloud-runtime endpoints in openapi.yaml Add five cloud-runtime FE-facing endpoints to the OSS spec with deprecated: true and standardized description prefixes: - GET /api/history_v2 — superseded by GET /api/jobs - GET /api/history_v2/{prompt_id} — superseded by GET /api/jobs/{prompt_id} - GET /api/logs — returns static placeholder; no real log data - GET /api/viewvideo — alias of GET /api/view for legacy video playback - GET /api/job/{job_id}/status — superseded by GET /api/jobs/{job_id} Each endpoint is tagged x-runtime: [cloud] and follows the same deprecation convention established for /api/history endpoints. Co-authored-by: Matt Miller * fix(spec): consolidate duplicate path entries on deprecated cloud-runtime endpoints Previous commit added new path entries with `deprecated: true` for `/api/job/{job_id}/status`, `/api/history_v2`, `/api/history_v2/{prompt_id}`, `/api/logs`, and `/api/viewvideo`, but the canonical entries already existed elsewhere in the file. Result: 5 duplicate path keys (Spectral parser errors), and the deprecation flag did not land on the operations that FE clients consume by operationId. This commit moves `deprecated: true` plus the standardized "Deprecated." description onto the canonical operations (`getCloudJobStatus`, `getHistoryV2`, `getHistoryV2ByPromptId`, `getCloudLogs`, `viewVideo`) and removes the duplicate entries. Operation IDs and response schemas are unchanged. Spectral lint passes with zero new warnings. --- openapi.yaml | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/openapi.yaml b/openapi.yaml index d4c9e67ca..96be4c1d5 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -2071,7 +2071,6 @@ paths: type: integer description: Number of assets marked as missing - # =========================================================================== # Cloud-runtime FE-facing operations # @@ -2122,7 +2121,11 @@ paths: operationId: getCloudJobStatus tags: [queue] summary: Get status of a cloud job - description: "[cloud-only] Returns the current execution status of a cloud job." + deprecated: true + description: | + **Deprecated.** This endpoint is superseded by `GET /api/jobs/{job_id}`. + Clients should migrate; the endpoint is retained for backward + compatibility but will be removed in a future release. x-runtime: [cloud] parameters: - name: job_id @@ -2192,7 +2195,11 @@ paths: operationId: getHistoryV2 tags: [history] summary: Get paginated execution history (v2) - description: "[cloud-only] Returns a paginated list of execution history entries in the v2 format, with richer metadata than the legacy history endpoint." + deprecated: true + description: | + **Deprecated.** This endpoint is superseded by `GET /api/jobs`. + Clients should migrate; the endpoint is retained for backward + compatibility but will be removed in a future release. x-runtime: [cloud] parameters: - name: limit @@ -2231,7 +2238,11 @@ paths: operationId: getHistoryV2ByPromptId tags: [history] summary: Get v2 history for a specific prompt - description: "[cloud-only] Returns the v2 history entry for a specific prompt execution." + deprecated: true + description: | + **Deprecated.** This endpoint is superseded by `GET /api/jobs/{prompt_id}`. + Clients should migrate; the endpoint is retained for backward + compatibility but will be removed in a future release. x-runtime: [cloud] parameters: - name: prompt_id @@ -2266,7 +2277,12 @@ paths: operationId: getCloudLogs tags: [system] summary: Get cloud execution logs - description: "[cloud-only] Returns execution logs for the authenticated user's cloud jobs." + deprecated: true + description: | + **Deprecated.** This endpoint returns a static placeholder response and + provides no real log data. It is retained only to avoid breaking clients + that still call it. Clients should remove their dependency; the endpoint + will be removed in a future release. x-runtime: [cloud] parameters: - name: job_id @@ -5370,7 +5386,12 @@ paths: operationId: viewVideo tags: [view] summary: View or download a video file - description: "[cloud-only] Serves a video file from the output directory. Used by the frontend video player." + deprecated: true + description: | + **Deprecated.** This endpoint is an alias of `GET /api/view` added for + legacy history-queue video playback. Callers should use `/api/view` + directly; the endpoint is retained for backward compatibility but will + be removed in a future release. x-runtime: [cloud] parameters: - name: filename @@ -5523,7 +5544,6 @@ paths: schema: $ref: "#/components/schemas/CloudError" - components: parameters: ComfyUserHeader: @@ -6875,7 +6895,6 @@ components: error: type: string - # ------------------------------------------------------------------- # Cloud-runtime schemas # From a5f7bc5658fdc97de7478882b3a0f5aa2d765506 Mon Sep 17 00:00:00 2001 From: Matt Miller Date: Tue, 12 May 2026 13:14:50 -0700 Subject: [PATCH 2/4] Suppress false-positive Spectral lint on WebSocket endpoint (#13842) The /ws path uses HTTP 101 (Switching Protocols), which is the correct response for a WebSocket upgrade but not a 2xx. The built-in operation-success-response rule fires as a false positive because OpenAPI 3.x has no native WebSocket support. Add a path-scoped override in .spectral.yaml to disable the rule for /ws only, leaving it active for all other operations. --- .spectral.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.spectral.yaml b/.spectral.yaml index 4bb4a4a94..a4b137628 100644 --- a/.spectral.yaml +++ b/.spectral.yaml @@ -89,3 +89,12 @@ rules: then: field: description function: truthy + +overrides: + # /ws uses HTTP 101 (Switching Protocols) — a legitimate response for a + # WebSocket upgrade, but not a 2xx, so operation-success-response fires + # as a false positive. OpenAPI 3.x has no native WebSocket support. + - files: + - "openapi.yaml#/paths/~1ws" + rules: + operation-success-response: off From 1d95ed211e0d971a1bfa76330a079d03eff9c802 Mon Sep 17 00:00:00 2001 From: drozbay <17261091+drozbay@users.noreply.github.com> Date: Tue, 12 May 2026 16:57:31 -0600 Subject: [PATCH 3/4] Fix LTXV mid-video multi-frame guide alignment (CORE-129) (#13625) --- comfy_extras/nodes_lt.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/comfy_extras/nodes_lt.py b/comfy_extras/nodes_lt.py index a4c85db77..3dc1199c2 100644 --- a/comfy_extras/nodes_lt.py +++ b/comfy_extras/nodes_lt.py @@ -338,8 +338,25 @@ class LTXVAddGuide(io.ComfyNode): noise_mask = get_noise_mask(latent) _, _, latent_length, latent_height, latent_width = latent_image.shape + + # For mid-video multi-frame guides, prepend+strip a throwaway first frame so the VAE's "first latent = 1 pixel frame" asymmetry lands on the discarded slot + time_scale_factor = scale_factors[0] + num_frames_to_keep = ((image.shape[0] - 1) // time_scale_factor) * time_scale_factor + 1 + resolved_frame_idx = frame_idx + if frame_idx < 0: + _, num_keyframes = get_keyframe_idxs(positive) + resolved_frame_idx = max((latent_length - num_keyframes - 1) * time_scale_factor + 1 + frame_idx, 0) + causal_fix = resolved_frame_idx == 0 or num_frames_to_keep == 1 + + if not causal_fix: + image = torch.cat([image[:1], image], dim=0) + image, t = cls.encode(vae, latent_width, latent_height, image, scale_factors) + if not causal_fix: + t = t[:, :, 1:, :, :] + image = image[1:] + frame_idx, latent_idx = cls.get_latent_index(positive, latent_length, len(image), frame_idx, scale_factors) assert latent_idx + t.shape[2] <= latent_length, "Conditioning frames exceed the length of the latent sequence." @@ -352,6 +369,7 @@ class LTXVAddGuide(io.ComfyNode): t, strength, scale_factors, + causal_fix=causal_fix, ) # Track this guide for per-reference attention control. From 300b6c8c9186cfcd4b2b2c51ec0afd4449e7fbb7 Mon Sep 17 00:00:00 2001 From: comfyanonymous <121283862+comfyanonymous@users.noreply.github.com> Date: Tue, 12 May 2026 17:28:20 -0700 Subject: [PATCH 4/4] Revert some breaking changes. (#13861) --- comfy_extras/nodes_mask.py | 27 ++++++--------------------- 1 file changed, 6 insertions(+), 21 deletions(-) diff --git a/comfy_extras/nodes_mask.py b/comfy_extras/nodes_mask.py index c9b2a84d9..96ee1a0f8 100644 --- a/comfy_extras/nodes_mask.py +++ b/comfy_extras/nodes_mask.py @@ -40,23 +40,13 @@ def composite(destination, source, x, y, mask = None, multiplier = 8, resize_sou inverse_mask = torch.ones_like(mask) - mask - source_rgb = source[:, :3, :visible_height, :visible_width] - dest_slice = destination[..., top:bottom, left:right] - - if destination.shape[1] == 4: - if torch.max(dest_slice) == 0: - destination[:, :3, top:bottom, left:right] = source_rgb - destination[:, 3:4, top:bottom, left:right] = mask - else: - destination[:, :3, top:bottom, left:right] = (mask * source_rgb) + (inverse_mask * dest_slice[:, :3]) - destination[:, 3:4, top:bottom, left:right] = torch.max(mask, dest_slice[:, 3:4]) - else: - source_portion = mask * source_rgb - destination_portion = inverse_mask * dest_slice - destination[..., top:bottom, left:right] = source_portion + destination_portion + source_portion = mask * source[..., :visible_height, :visible_width] + destination_portion = inverse_mask * destination[..., top:bottom, left:right] + destination[..., top:bottom, left:right] = source_portion + destination_portion return destination + class LatentCompositeMasked(IO.ComfyNode): @classmethod def define_schema(cls): @@ -95,23 +85,18 @@ class ImageCompositeMasked(IO.ComfyNode): display_name="Image Composite Masked", category="image", inputs=[ + IO.Image.Input("destination"), IO.Image.Input("source"), IO.Int.Input("x", default=0, min=0, max=nodes.MAX_RESOLUTION, step=1), IO.Int.Input("y", default=0, min=0, max=nodes.MAX_RESOLUTION, step=1), IO.Boolean.Input("resize_source", default=False), - IO.Image.Input("destination", optional=True), IO.Mask.Input("mask", optional=True), ], outputs=[IO.Image.Output()], ) @classmethod - def execute(cls, source, x, y, resize_source, destination = None, mask = None) -> IO.NodeOutput: - if destination is None: # transparent rgba - B, H, W, C = source.shape - destination = torch.zeros((B, H, W, 4), dtype=source.dtype, device=source.device) - if C == 3: - source = torch.nn.functional.pad(source, (0, 1), value=1.0) + def execute(cls, destination, source, x, y, resize_source, mask = None) -> IO.NodeOutput: destination, source = node_helpers.image_alpha_fix(destination, source) destination = destination.clone().movedim(-1, 1) output = composite(destination, source.movedim(-1, 1), x, y, mask, 1, resize_source).movedim(1, -1)