Skip to content

fix(print): promoted widgets print at the host address; auto-grow groups print as links - #816

Closed
skishore23 wants to merge 7 commits into
mainfrom
kishore/print-dynamic-subgraph
Closed

fix(print): promoted widgets print at the host address; auto-grow groups print as links#816
skishore23 wants to merge 7 commits into
mainfrom
kishore/print-dynamic-subgraph

Conversation

@skishore23

Copy link
Copy Markdown
Contributor

Stacked on #815 (kishore/be-10305-…) → #812#809. Retarget as they land.

Why

comfy workflow print is how the agent reads a canvas before editing it. It was wrong for the two node shapes the last two PRs fixed:

  • Subgraph instances: promoted values were read positionally from the instance's serialized inputs[] (only what the UI happened to show) instead of per widget-backed declared input the way the frontend does. Captured on real gallery templates: audio_minimax_music_3 printed the caption under switch= and hid caption/lyrics/seed/models; api_seedance2_5_video_extend printed drop_audio="lanczos" (shifted) and dropped interpolation/padding_color; image_z_image_turbo printed text=None with all eight promoted widgets invisible and interior lines with no editable address.
  • Auto-grow groups: an agent-built GeminiNanoBanana2V2's model.images group was invisible; an agent-built BatchImagesNode printed a phantom images=None.

Change (comfy_cli/workflow_print.py)

  • Instance line: one argument per declared subgraph input in declaration order; promoted widgets print promoted.effective_value (host value, else interior default) under the address set-widget takes (57.width); an outside link into a promoted input prints as the link; sockets print their link; declared-but-unbacked inputs fall back to proxyWidgets exactly as slots does.
  • Definition block: interior lines keep IN.width; the header gains promoted widgets: IN.text, IN.width, … — each is the instance's own value: edit 57.<name>, never 57/<id>.<name>.
  • Auto-grow: groups from Graph.autogrow_groups for the node's current selection; grown slots print as links; when no free slot exists and the schema allows one, the next schema name prints as an open slot (the free trailing slot the frontend itself keeps — and exactly what connect grows next); the line comment names the group and element type (model.images grows IMAGE (max 14)). Widgets are never shifted.

Red → green

tests/comfy_cli/test_workflow_print_promoted_autogrow.py: 21 cases written first, 17 failed on the base (4 pin already-correct value-aware naming/selector behaviour). One golden line in test_workflow_print.py updated for the group annotation.

Verification

Full suite 6259 passed, 38 skipped; ruff 0.15.15 check + format clean.

🤖 Generated with Claude Code

@dosubot dosubot Bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Aug 28, 2026
@coderabbitai

coderabbitai Bot commented Aug 28, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

Next included review available in 43 seconds.

View limit details

Limit details: You’ve used all 4 included reviews currently available. Your 65 included PR review attempts over the past 7 days set your current allowance at 4 reviews per hour.

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).

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 287eee75-ef86-41bd-9490-8415597eedfb

📥 Commits

Reviewing files that changed from the base of the PR and between 74501f3 and 5e2e69c.

📒 Files selected for processing (9)
  • comfy_cli/cql/engine.py
  • comfy_cli/cql/promoted.py
  • comfy_cli/workflow_ops.py
  • comfy_cli/workflow_print.py
  • comfy_cli/workflow_to_api.py
  • tests/comfy_cli/command/test_workflow_edit_promoted.py
  • tests/comfy_cli/cql/test_promoted_inputs.py
  • tests/comfy_cli/test_workflow_print_promoted_autogrow.py
  • tests/comfy_cli/test_workflow_to_api_promoted.py

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: ac160006-48fc-460c-9ffb-3453468b9a36

📥 Commits

Reviewing files that changed from the base of the PR and between 6ebc303 and 74501f3.

📒 Files selected for processing (3)
  • comfy_cli/workflow_print.py
  • tests/comfy_cli/test_workflow_print.py
  • tests/comfy_cli/test_workflow_print_promoted_autogrow.py

Included review availability: 2 reviews are currently available. Your included PR review attempts over the past 7 days set your current allowance at 6 reviews per hour.


📝 Walkthrough

Walkthrough

The workflow printer now derives value-aware widget names, renders auto-grow groups, resolves promoted subgraph values across nesting levels, preserves stable addresses, and reports promoted widget locations. Tests cover rendering, CLI compatibility, malformed definitions, recursion bounds, and workflow immutability.

Changes

Workflow rendering

Layer / File(s) Summary
Value-aware and auto-grow rendering
comfy_cli/workflow_print.py, tests/comfy_cli/test_workflow_print.py, tests/comfy_cli/test_workflow_print_promoted_autogrow.py
Catalog data now controls positional widget values and active auto-grow groups. Growable groups render link slots, one available open slot when applicable, and group annotations without modifying the workflow.
Promoted subgraph resolution
comfy_cli/workflow_print.py, tests/comfy_cli/test_workflow_print_promoted_autogrow.py
Subgraph instances resolve promoted values from host overrides or interior defaults. Rendering preserves links, primitive values, proxy fallbacks, nested promotion, and bounded self-references. Definition headers identify promoted widget locations.
CLI and robustness validation
tests/comfy_cli/test_workflow_print_promoted_autogrow.py
Tests verify CLI address compatibility, external links, set_widget updates, missing catalogs, malformed definitions, unresolved nested values, and workflow immutability.

Sequence Diagram(s)

sequenceDiagram
  participant CLI
  participant workflow_print.py
  participant Catalog
  participant PromotedDefinitionIndex
  participant RenderedPython
  CLI->>workflow_print.py: print workflow
  workflow_print.py->>Catalog: derive widget and auto-grow metadata
  workflow_print.py->>PromotedDefinitionIndex: resolve promoted values
  Catalog-->>workflow_print.py: return widget ordering and active groups
  PromotedDefinitionIndex-->>workflow_print.py: return effective host or interior values
  workflow_print.py-->>RenderedPython: emit arguments, links, annotations, and headers
Loading

Merge Risk: ⚪ Minimal · up to 74501

The PR corrects workflow printing for promoted widgets and auto-grow groups, with the supplied verification reporting the full suite passing and lint/format checks clean. No actionable merge-blocking risk remains after normal checks.

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch kishore/print-dynamic-subgraph
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch kishore/print-dynamic-subgraph

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

@skishore23

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 28, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@skishore23
skishore23 force-pushed the kishore/be-10305-agent-editing-subgraph-human-tech-review-needed branch from bfb9b70 to dea39a1 Compare August 28, 2026 08:14
@skishore23
skishore23 force-pushed the kishore/print-dynamic-subgraph branch from 74501f3 to ef0ffc3 Compare August 28, 2026 08:15
@skishore23

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 28, 2026

Copy link
Copy Markdown
⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@skishore23

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 28, 2026

Copy link
Copy Markdown
⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@skishore23
skishore23 requested a review from annehe9 August 28, 2026 17:20
@skishore23
skishore23 force-pushed the kishore/be-10305-agent-editing-subgraph-human-tech-review-needed branch from dea39a1 to 12a8904 Compare August 28, 2026 17:22
@skishore23
skishore23 force-pushed the kishore/print-dynamic-subgraph branch from ef0ffc3 to 1e2e466 Compare August 28, 2026 17:22
@dosubot dosubot Bot added the lgtm This PR has been approved by a maintainer label Aug 28, 2026
@annehe9

annehe9 commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Medium, not blocking: Perf: effective_value rebuilds the definition index once per promoted widget

workflow_print.py:884 calls the public promoted.effective_value(...), which re-derives defs_by_id(workflow) and then re-resolves the input by name via find_promotedpromoted_inputs(sg, defs). Both are already available at the call site: _State.promoted_defs caches the index (workflow_print.py:1048), and the enclosing loop at workflow_print.py:874 already holds the PromotedInput as pi.

Measured on a synthetic workflow of 200 image_z_image_turbo instances (8 promoted widgets each), against object_info_subgraph_promoted.json:

base (kishore/be-10305-…) this PR
render_py wall clock 41 ms 251 ms

cProfile attributes 8.79 s of a 10.4 s profiled render to effective_value. Call counts for a 50-instance version of the same workflow:

function calls needed
promoted.promoted_inputs 451 51
promoted.defs_by_id 401 1

The 400 extra promoted_inputs calls are all find_promoted re-locating a pi the caller already has.

promoted._effective already takes defs, so this is close to a one-liner — an entry point that skips the name lookup:

def effective_value_for(workflow, instance, sg, pi, graph, defs):
    """`effective_value` when the caller already resolved `pi` and `defs`."""
    value = host_value(instance, pi)
    return value if value is not UNSET else source_value(workflow, sg, pi, graph, defs)

and at the call site:

value = _promoted.effective_value_for(state.workflow, node, sg_def, pi, ctx.graph, state.promoted_defs)

Note this drops the ValueError the current except at workflow_print.py:885 catches. That handler is unreachable anyway: pi comes from promoted_inputs(sg_def, …) on the same definition the caller resolved, and _render_nodes only reaches this path when is_subgraph_uuid(t) holds and defs_by_id[t] exists, so neither "not a promoted input" nor "not a subgraph instance" can fire.

Not a blocker. Real templates have a handful of instances, so 250 ms is invisible today. Worth doing before anything starts printing generated or batched workflows.

Rest of the PR checks out. I rendered all six gallery fixtures on base vs this head: every case in the description reproduces, no exceptions, no new warnings. The frontend_injected fix is a real one — LoadVideo(file="sloth_bullfight.mp4", control_after_generate="image") becomes LoadVideo(file="sloth_bullfight.mp4").

skishore23 added a commit that referenced this pull request Aug 28, 2026
…ut, not by name

Review on #816: the instance line called `promoted.effective_value(workflow,
instance, name, graph)` once per promoted widget, and that entry point
re-derives `defs_by_id(workflow)` and relocates the input via
`find_promoted` -> `promoted_inputs` — both of which the caller already held
(`_State.promoted_defs`, and the enclosing loop's `pi`). On 50 z-image
instances that was 451 `promoted_inputs` walks for 51 needed; 200 instances
rendered in 251 ms against 41 ms on base.

Add `promoted.effective_value_for(workflow, instance, sg, pi, graph, defs)`
— host value if materialized, else the interior source value — and make
`_effective` delegate to it so the by-name path is unchanged. The printer
uses it at the call site. The `ValueError` handler there is dropped: `pi`
comes from `promoted_inputs` on the very definition `_render_nodes` resolved
(`is_subgraph_uuid(t)` and `defs_by_id[t]` both hold), so neither "not a
promoted input" nor "not a subgraph instance" could fire.

Pinned by a test that counts `promoted_inputs` / `defs_by_id` calls while
rendering 40 instances (<= N+2 and <= 2), plus a unit test that
`effective_value_for` never re-walks `promoted_inputs` and agrees with
`effective_value` for both the interior-default and host-value cases.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@skishore23
skishore23 force-pushed the kishore/be-10305-agent-editing-subgraph-human-tech-review-needed branch from 335bd91 to 8365663 Compare August 28, 2026 23:48
@skishore23

Copy link
Copy Markdown
Contributor Author

Addressed in 99428ac — thanks for measuring it.

  • promoted.effective_value_for(workflow, instance, sg, pi, graph, defs) added (host value if materialized, else source_value); _effective now delegates to it, so the by-name effective_value path is unchanged.
  • The instance line uses it with the loop's own pi and _State.promoted_defs. The ValueError handler is gone for the reason you gave: pi comes from promoted_inputs on the definition _render_nodes already resolved, so neither branch could fire.
  • Pinned: test_promoted_values_resolve_once_per_instance_not_per_widget renders 40 z-image instances with promoted_inputs / defs_by_id counted and asserts <= N+2 / <= 2 (red on the previous head: 361 / 321). Plus test_effective_value_for_skips_the_name_lookup in tests/comfy_cli/cql/test_promoted_inputs.py (never re-walks promoted_inputs; agrees with effective_value for both the interior-default and host-value cases).
  • Your 200-instance synthetic: 251 ms -> 29 ms here.

Print/cql/slots/edit suites: 550 passed; ruff 0.15.15 check/format clean.

skishore23 added a commit that referenced this pull request Aug 28, 2026
…ut, not by name

Review on #816: the instance line called `promoted.effective_value(workflow,
instance, name, graph)` once per promoted widget, and that entry point
re-derives `defs_by_id(workflow)` and relocates the input via
`find_promoted` -> `promoted_inputs` — both of which the caller already held
(`_State.promoted_defs`, and the enclosing loop's `pi`). On 50 z-image
instances that was 451 `promoted_inputs` walks for 51 needed; 200 instances
rendered in 251 ms against 41 ms on base.

Add `promoted.effective_value_for(workflow, instance, sg, pi, graph, defs)`
— host value if materialized, else the interior source value — and make
`_effective` delegate to it so the by-name path is unchanged. The printer
uses it at the call site. The `ValueError` handler there is dropped: `pi`
comes from `promoted_inputs` on the very definition `_render_nodes` resolved
(`is_subgraph_uuid(t)` and `defs_by_id[t]` both hold), so neither "not a
promoted input" nor "not a subgraph instance" could fire.

Pinned by a test that counts `promoted_inputs` / `defs_by_id` calls while
rendering 40 instances (<= N+2 and <= 2), plus a unit test that
`effective_value_for` never re-walks `promoted_inputs` and agrees with
`effective_value` for both the interior-default and host-value cases.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@skishore23
skishore23 force-pushed the kishore/print-dynamic-subgraph branch from 99428ac to 23cf0dc Compare August 28, 2026 23:50
@skishore23
skishore23 force-pushed the kishore/be-10305-agent-editing-subgraph-human-tech-review-needed branch from 8365663 to dce5206 Compare August 29, 2026 00:59
skishore23 added a commit that referenced this pull request Aug 29, 2026
…ut, not by name

Review on #816: the instance line called `promoted.effective_value(workflow,
instance, name, graph)` once per promoted widget, and that entry point
re-derives `defs_by_id(workflow)` and relocates the input via
`find_promoted` -> `promoted_inputs` — both of which the caller already held
(`_State.promoted_defs`, and the enclosing loop's `pi`). On 50 z-image
instances that was 451 `promoted_inputs` walks for 51 needed; 200 instances
rendered in 251 ms against 41 ms on base.

Add `promoted.effective_value_for(workflow, instance, sg, pi, graph, defs)`
— host value if materialized, else the interior source value — and make
`_effective` delegate to it so the by-name path is unchanged. The printer
uses it at the call site. The `ValueError` handler there is dropped: `pi`
comes from `promoted_inputs` on the very definition `_render_nodes` resolved
(`is_subgraph_uuid(t)` and `defs_by_id[t]` both hold), so neither "not a
promoted input" nor "not a subgraph instance" could fire.

Pinned by a test that counts `promoted_inputs` / `defs_by_id` calls while
rendering 40 instances (<= N+2 and <= 2), plus a unit test that
`effective_value_for` never re-walks `promoted_inputs` and agrees with
`effective_value` for both the interior-default and host-value cases.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@skishore23
skishore23 force-pushed the kishore/print-dynamic-subgraph branch from 23cf0dc to fd2ecc0 Compare August 29, 2026 00:59
@skishore23
skishore23 force-pushed the kishore/be-10305-agent-editing-subgraph-human-tech-review-needed branch from dce5206 to 14978e6 Compare August 29, 2026 01:42
skishore23 added a commit that referenced this pull request Aug 29, 2026
…ut, not by name

Review on #816: the instance line called `promoted.effective_value(workflow,
instance, name, graph)` once per promoted widget, and that entry point
re-derives `defs_by_id(workflow)` and relocates the input via
`find_promoted` -> `promoted_inputs` — both of which the caller already held
(`_State.promoted_defs`, and the enclosing loop's `pi`). On 50 z-image
instances that was 451 `promoted_inputs` walks for 51 needed; 200 instances
rendered in 251 ms against 41 ms on base.

Add `promoted.effective_value_for(workflow, instance, sg, pi, graph, defs)`
— host value if materialized, else the interior source value — and make
`_effective` delegate to it so the by-name path is unchanged. The printer
uses it at the call site. The `ValueError` handler there is dropped: `pi`
comes from `promoted_inputs` on the very definition `_render_nodes` resolved
(`is_subgraph_uuid(t)` and `defs_by_id[t]` both hold), so neither "not a
promoted input" nor "not a subgraph instance" could fire.

Pinned by a test that counts `promoted_inputs` / `defs_by_id` calls while
rendering 40 instances (<= N+2 and <= 2), plus a unit test that
`effective_value_for` never re-walks `promoted_inputs` and agrees with
`effective_value` for both the interior-default and host-value cases.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@skishore23
skishore23 force-pushed the kishore/print-dynamic-subgraph branch from fd2ecc0 to 1ac6315 Compare August 29, 2026 01:42
@skishore23
skishore23 force-pushed the kishore/be-10305-agent-editing-subgraph-human-tech-review-needed branch from 14978e6 to bd27441 Compare August 29, 2026 01:54
skishore23 added a commit that referenced this pull request Aug 29, 2026
…ut, not by name

Review on #816: the instance line called `promoted.effective_value(workflow,
instance, name, graph)` once per promoted widget, and that entry point
re-derives `defs_by_id(workflow)` and relocates the input via
`find_promoted` -> `promoted_inputs` — both of which the caller already held
(`_State.promoted_defs`, and the enclosing loop's `pi`). On 50 z-image
instances that was 451 `promoted_inputs` walks for 51 needed; 200 instances
rendered in 251 ms against 41 ms on base.

Add `promoted.effective_value_for(workflow, instance, sg, pi, graph, defs)`
— host value if materialized, else the interior source value — and make
`_effective` delegate to it so the by-name path is unchanged. The printer
uses it at the call site. The `ValueError` handler there is dropped: `pi`
comes from `promoted_inputs` on the very definition `_render_nodes` resolved
(`is_subgraph_uuid(t)` and `defs_by_id[t]` both hold), so neither "not a
promoted input" nor "not a subgraph instance" could fire.

Pinned by a test that counts `promoted_inputs` / `defs_by_id` calls while
rendering 40 instances (<= N+2 and <= 2), plus a unit test that
`effective_value_for` never re-walks `promoted_inputs` and agrees with
`effective_value` for both the interior-default and host-value cases.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@skishore23
skishore23 force-pushed the kishore/print-dynamic-subgraph branch from 1ac6315 to b07860e Compare August 29, 2026 01:54
skishore23 and others added 4 commits August 28, 2026 18:55
…the host, or the node linked to it (BE-10305)

The frontend (ComfyUI_frontend ADR 0009 "Subgraph promoted widgets use
linked inputs", SubgraphNode.ts) represents a promoted widget as a linked
subgraph input whose value the HOST instance owns: `widgets_values[i]` on
the instance, consumed positionally by the i-th subgraph input that resolves
to an interior widget. The interior widget is only the default; "the
host/exterior value wins over the interior/source value during repair,
persistence, and prompt serialization". The CLI still followed the legacy
`properties.proxyWidgets` list into the interior node for every read and
write, so on the z-image turbo template `set-widget 57.width 768` edited a
value the frontend neither ran nor displayed (scenario 1), an outside
PrimitiveInt could not be wired to `57.width` (scenario 2), and `comfy run`
/ `validate` submitted the interior prompt on post-migration templates —
`audio_minimax_music_3` ships an interior caption of '' while the host holds
the whole prompt.

cql/promoted.py (new) is the host-owned value model, pure over workflow
JSON: `promoted_inputs` (the frontend's own rule — first boundary link whose
target is a widget-backed input or a nested instance's promoted input;
sockets own no slot), `host_value` (quarantined hostValue first, then the
positional slot), `effective_value`, `set_host_value` (materializes the host
array in declaration order, never touches the definition), and
`resolve_write`: `<instance>.<input>` → host; an outside link feeding it →
its primitive (PrimitiveInt / PrimitiveString* / legacy PrimitiveNode,
through Reroutes), refused with the driver named when a non-primitive
computes the value; `<instance>/<inner>.<w>` fed by the subgraph input node
→ the same host (op carries `redirected_from`); fed by another interior node
→ refused; anything else → the definition, as before. Legacy proxies that
the definition does not declare as inputs keep the interior route.

set-widget / set-slot / vary share that resolution. connect materializes a
declared input on the instance (with the frontend's `widget` marker) and
wires it, type-checked against the declared type. slots advertises promoted
widgets at the instance address with the value the frontend runs, flags
link-driven widgets (`linked_from`), keeps unpromoted interior widgets
reachable (nested instances included) and no longer advertises the interior
address behind a promotion. convert_ui_to_api overlays host values onto the
expanded interior nodes with the frontend's precedence (outside link, host,
interior); inner instances first so an outer host wins.

Verified against the workflow_templates corpus: 75/75 instances that carry
host values match the positional rule with 0 mismatches. Fixtures are
verbatim gallery templates plus a trimmed cloud-catalog object_info.

The host-write op carries `promoted.value_index` and the materialized
`promoted.host_widgets_values`: the comfy-multi-player applier stores a
subgraph instance opaquely and rejects a named write to an uncatalogued
class, so the in-app path needs a matching applier branch (tracked on
BE-10305).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ce; wrap list host values; tolerate untyped declared inputs

Review findings on #815. `host_widgets_values` was read from the instance
dict captured during resolution — for a nested host inside a definition
shared by two instances, apply forks the definition on the way down and
writes the copy, so the op carried the pre-write array and an opaque
applier would have replaced the host state with stale values. The payload
is now read from the instance apply wrote. The converter overlay wraps host
values like every other widget value, so a two-item list host value is not
read back as a `[node, slot]` link. A declared subgraph input with no usable
type reads as unknown ("") rather than the repr "None": connect skips the
type check and stamps the slot with the source type instead of refusing a
valid wire with a misleading mismatch. The reuse test now proves the earlier
link was replaced.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…t docstrings

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…everywhere

Review (annehe9) on #815: `resolve_write` already checked that the link id
serialized on a promoted input still exists (the frontend drops a dangling
link on load, so the input is unlinked and the host value runs), but the
converter overlay and `slots` used the raw id — so `set-widget` wrote the
host while `comfy run` submitted the interior default and `slots` pointed
at a source node that does not exist. One helper, `live_external_link`,
now decides for all three; the converter and the slots walk thread the
link scope through (the workflow for a top-level instance, the containing
definition for a nested one, whose links are dicts rather than arrays).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@skishore23
skishore23 force-pushed the kishore/be-10305-agent-editing-subgraph-human-tech-review-needed branch from bd27441 to d208cf9 Compare August 29, 2026 01:55
@skishore23
skishore23 changed the base branch from kishore/be-10305-agent-editing-subgraph-human-tech-review-needed to main August 29, 2026 01:55
@dosubot dosubot Bot added size:XXL This PR changes 1000+ lines, ignoring generated files. and removed size:L This PR changes 100-499 lines, ignoring generated files. labels Aug 29, 2026
skishore23 and others added 3 commits August 28, 2026 19:03
…ups print as links

`comfy workflow print` rendered two things wrongly:

* A subgraph instance read its promoted widget values positionally off the
  instance's serialized `inputs[]` entries that carried a `widget` marker —
  the frontend stores them positionally per widget-backed DECLARED input
  (ADR 0009, cql.promoted). Post-migration templates therefore printed the
  caption under `switch=` (audio_minimax_music_3), a boolean under
  `drop_audio="lanczos"` (api_seedance2_5_video_extend), and a
  pre-migration template (image_z_image_turbo) showed `text=None` with every
  other promoted widget invisible — while the interior lines showed only
  `IN.width`, so the reader had no value and no address to edit.

  The instance line now prints one argument per declared subgraph input in
  declaration order: a promoted widget shows its EFFECTIVE value
  (`promoted.effective_value`: host value, else interior default) under the
  address `set-widget` takes (`57.width`); an outside link into it prints as
  that link (`width=primitive_int`), with the same PrimitiveNode/Reroute
  markers a regular node gets; sockets print their link. A declared input
  no boundary link backs falls back to `proxyWidgets`, exactly as `slots`
  does. Interior lines keep `IN.<name>`, and the definition header gains a
  `promoted widgets: IN.width, … — edit 57.<name>, never 57/<id>.<name>` line.
  Nested promotion resolves through `promoted_inputs`' bounded recursion.

* An auto-grow group (`COMFY_AUTOGROW_V3`, e.g. `model.images` under a
  dynamic combo) was invisible on an agent-built node (`add_node` writes no
  grown slots), and a top-level group's base entry printed as a phantom
  `images=None` link input. The group's grown slots now print as links, one
  open slot stays visible under the exact name `connect` grows next
  (`"model.images.image_3": None` — the free trailing slot the frontend
  itself keeps), and the line comment names the group with its element type
  and capacity (`model.images grows IMAGE (max 14)`). Groups follow the
  node's current selection via `Graph.autogrow_groups`; widgets stay at
  their value-aware names and positions, never shifted.

Tests: tests/comfy_cli/test_workflow_print_promoted_autogrow.py (21 cases,
written red-first against the gallery fixtures and
object_info_nested_autogrow), plus the seedream golden line updated for the
group annotation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…erate

Every schema-less positional entry printed under the name
control_after_generate, so an older save's LoadImage upload slot
(["a.png", "image"]) printed as control_after_generate="image". Injected
slots (upload / audioUI / PREVIEW_3D image) carry the new
frontend_injected flag from #809: they are walked for position and never
printed, matching slots (which omits them) and every write surface (which
refuses them).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ut, not by name

Review on #816: the instance line called `promoted.effective_value(workflow,
instance, name, graph)` once per promoted widget, and that entry point
re-derives `defs_by_id(workflow)` and relocates the input via
`find_promoted` -> `promoted_inputs` — both of which the caller already held
(`_State.promoted_defs`, and the enclosing loop's `pi`). On 50 z-image
instances that was 451 `promoted_inputs` walks for 51 needed; 200 instances
rendered in 251 ms against 41 ms on base.

Add `promoted.effective_value_for(workflow, instance, sg, pi, graph, defs)`
— host value if materialized, else the interior source value — and make
`_effective` delegate to it so the by-name path is unchanged. The printer
uses it at the call site. The `ValueError` handler there is dropped: `pi`
comes from `promoted_inputs` on the very definition `_render_nodes` resolved
(`is_subgraph_uuid(t)` and `defs_by_id[t]` both hold), so neither "not a
promoted input" nor "not a subgraph instance" could fire.

Pinned by a test that counts `promoted_inputs` / `defs_by_id` calls while
rendering 40 instances (<= N+2 and <= 2), plus a unit test that
`effective_value_for` never re-walks `promoted_inputs` and agrees with
`effective_value` for both the interior-default and host-value cases.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@skishore23
skishore23 force-pushed the kishore/print-dynamic-subgraph branch from b07860e to 5e2e69c Compare August 29, 2026 02:03
@skishore23

Copy link
Copy Markdown
Contributor Author

Consolidated: this PR's commits land on main via #820 (the stack head, retargeted to main and rebase-merged so each reviewed commit is preserved individually). Approved content unchanged.

@skishore23 skishore23 closed this Aug 29, 2026
@github-actions github-actions Bot locked and limited conversation to collaborators Aug 29, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

bug Something isn't working lgtm This PR has been approved by a maintainer size:XXL This PR changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants