Skip to content

fix(workflow_to_api): widget-aware dynamic-combo expansion + name-aligned control-marker filtering (BE-3370)#558

Open
mattmillerai wants to merge 2 commits into
mainfrom
matt/be-3370-widget-aware-dynamic-combo
Open

fix(workflow_to_api): widget-aware dynamic-combo expansion + name-aligned control-marker filtering (BE-3370)#558
mattmillerai wants to merge 2 commits into
mainfrom
matt/be-3370-widget-aware-dynamic-combo

Conversation

@mattmillerai

Copy link
Copy Markdown
Collaborator

ELI-5

When you run comfy run --workflow on a fresh "Seedream" template, comfy-cli
rewrites the visual workflow into the API JSON the server wants. It got two
things wrong on nodes that use the new V3 dynamic combo widget (a dropdown
that reveals different sub-widgets depending on what you pick):

  1. It counted a connection-only sub-input (the reference-image list) as if it
    were a knob with a saved value. There's no saved value for it, so every knob
    after it slid down one slot — the seed's value ended up in a made-up
    model.images key.
  2. A separate pass that strips the invisible "randomize/fixed" marker after a
    seed didn't account for the dropdown's hidden sub-widgets, so it looked at
    the wrong slot and let "randomize" through as the seed value.

Result: the server rejected the job (seed: expected INT, got str). This PR
makes both passes into one walk that expands the dropdown correctly and
strips the marker in the right place, so a pristine template converts cleanly.

What changed

  • _collect_widget_inputs now uses a single _schema_widget_pairs walk when a
    schema is available. It:
    • applies the same _is_widget_input test to dynamic-combo sub-inputs as to
      top-level inputs, so connection-only subs (e.g. Seedream's images,
      COMFY_AUTOGROW_V3) consume no widgets_values slot;
    • recurses when a sub-input is itself a dynamic combo;
    • drops the control_after_generate marker inline as each widget is consumed
      (explicit flag, or an implicit INT seed/noise_seed), so name-order and
      marker-filtering can never disagree.
  • _get_widget_name_order + _dynamic_combo_sub_inputs are replaced by
    _schema_widget_pairs + _dynamic_combo_selected_subs (spec-returning).
  • Schema-less / unknown nodes are unchanged: they still fall back to
    _filter_control_values + _fallback_widget_names.
  • Output shape is unchanged: flat dotted keys (model.size_preset) on the
    node's API inputs; connection subs arrive via the node's inputs array as
    dotted entries (model.images.image_1), not the widget map.

Tests

  • New fixtures: object_info_bytedance_seedream_v2.json (faithful /object_info
    entry built per ComfyUI's nodes_bytedance.py define_schema /
    _seedream_model_inputs and the comfy_api V3 serialization) and
    seedream_5_0_pro_t2i_ui.json (vendored copy of the pristine
    api_bytedance_seedream_5_0_pro_t2i.json template).
  • TestSeedreamDynamicCombo:
    • pristine pro-t2i template → model == "seedream 5.0 pro", model.size_preset,
      model.width/height, seed == 0 (int), watermark False, thinking True
      (schema default), and no model.images key / no "randomize".
    • batch-capable "seedream 5.0 lite" option → model.max_images == 2,
      model.fail_on_partial is False, seed == 7, watermark True, drops
      "fixed", no model.images.
    • Both fail before the fix and pass after (verified by stashing the source
      change).
  • Full existing suite (tests/comfy_cli/test_workflow_to_api.py, 94 tests,
    incl. TestDynamicComboAfterControlMarker) and tests/comfy_cli/command/test_run.py
    pass. ruff check + ruff format --check clean under CI-pinned ruff 0.15.15.

Notes for review

  • The riskiest aspect is that the unified walk now handles every schema-bearing
    node, not just dynamic combos. It applies the exact same predicates
    (_is_widget_input, _has_control_after_generate_companion) in the same
    schema-section order as the two functions it replaces; I traced it against the
    existing control-marker, implicit-seed, force-input, and wildcard tests and it
    reproduces identical output (whole suite green).
  • Recursion is bounded: each consume for a widget advances the value index by
    ≥1 before recursing, and non-widget sub-inputs don't recurse, so depth is
    bounded by len(widgets_values).
  • The hand-built fixture reflects the template's "seedream 5.0 pro" selector
    (per the ticket's serialization spec). The local ComfyUI checkout is a newer
    node revision where "pro" was renamed and all options share the batch shape;
    the fixture is faithful to the version the vendored template was saved from.
  • Negative-claim falsification (worker-brief step 3e): not applicable — this
    change denies no capability; it makes previously-failing conversions succeed
    and adds no throw/deny/"not supported" path.

…gned control-marker filtering (BE-3370)

A pristine COMFY_DYNAMICCOMBO_V3 template (e.g. Seedream) converted to
invalid API JSON: node 1 produced a phantom "model.images": 0 (stealing
the seed value) and "seed": "randomize" (unfiltered control marker), which
the server rejected (seed: expected INT, got str).

Two walks disagreed. _get_widget_name_order expanded a dynamic combo's
options into EVERY sub-input name — including connection-only subs like
Seedream's COMFY_AUTOGROW_V3 images, which own no widgets_values slot — so
every later widget shifted by one. _filter_control_values walked schema
inputs positionally WITHOUT dynamic expansion, so when a dynamic combo
preceded a control_after_generate seed it checked the companion at the wrong
index and the marker survived.

Unify both into a single ordered pass, _schema_widget_pairs: expand dynamic
combos in place (applying _is_widget_input to each sub-input so connection
subs are skipped; recursing for nested dynamic combos) and drop control
markers inline as each widget is consumed, so name order and marker filtering
can never disagree. Non-dynamic nodes keep identical behavior; unknown/
schema-less nodes fall back to the positional heuristic as before.

Add a faithful /object_info fixture for ByteDanceSeedreamNodeV2 and vendor
the pristine pro-t2i template; add regression tests for the pristine template
and a batch-capable option case (both fail before the fix).
@mattmillerai mattmillerai added the agent-coded PR authored by the agent-work loop label Jul 18, 2026
@coderabbitai

coderabbitai Bot commented Jul 18, 2026

Copy link
Copy Markdown

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 10 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 1471fb8c-a957-424a-867a-14b795fd9b94

📥 Commits

Reviewing files that changed from the base of the PR and between a732f5c and 9467b30.

📒 Files selected for processing (4)
  • comfy_cli/workflow_to_api.py
  • tests/comfy_cli/fixtures/object_info_bytedance_seedream_v2.json
  • tests/comfy_cli/fixtures/seedream_5_0_pro_t2i_ui.json
  • tests/comfy_cli/test_workflow_to_api.py
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch matt/be-3370-widget-aware-dynamic-combo
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch matt/be-3370-widget-aware-dynamic-combo

Comment @coderabbitai help to get the list of available commands.

@mattmillerai
mattmillerai marked this pull request as ready for review July 18, 2026 00:19
@dosubot dosubot Bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Jul 18, 2026
@mattmillerai mattmillerai added the cursor-review Request Cursor bot review label Jul 18, 2026
@dosubot dosubot Bot added the bug Something isn't working label Jul 18, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Cursor Review — Consolidated panel

Triggered by @mattmillerai.

Found 3 finding(s).

Severity Count
🟡 Medium 2
🟢 Low 1

Panel: 6/8 reviewers contributed findings.

Reviewers that did not contribute: kimi-k2.5:adversarial (empty), kimi-k2.5:edge-case (empty)

Comment thread comfy_cli/workflow_to_api.py Outdated
Comment thread comfy_cli/workflow_to_api.py
Comment thread comfy_cli/workflow_to_api.py Outdated
…w (BE-3370)

Address three cursor-review findings on the widget-aware dynamic-combo walk:

- Implicit control_after_generate companion for a seed/noise_seed *sub-input*
  now matches on the dotted name's leaf segment (model.seed), so its trailing
  "randomize"/"fixed" marker is stripped instead of shifting later sub-inputs.
- An unresolved dynamic-combo selector (value names no option in the current
  schema — model renamed/removed server-side or version skew) now logs a
  warning instead of silently misaligning every following widget.
- Bound the dynamic-combo recursion at depth 16 so a pathological nested chain
  from a third-party /object_info degrades to a warning rather than an uncaught
  RecursionError that aborts the whole conversion.

Adds regression tests for each.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent-coded PR authored by the agent-work loop bug Something isn't working cursor-review Request Cursor bot review size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant