Skip to content

fix: five CLI-fixable tool-error clusters from the Aug 25–28 Langfuse sweep - #820

Merged
skishore23 merged 19 commits into
mainfrom
kishore/prod-tool-errors-aug26
Aug 29, 2026
Merged

fix: five CLI-fixable tool-error clusters from the Aug 25–28 Langfuse sweep#820
skishore23 merged 19 commits into
mainfrom
kishore/prod-tool-errors-aug26

Conversation

@skishore23

@skishore23 skishore23 commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Consolidated stack head. After #809 and #825 (ex-#812) merged, the remaining reviewed PRs were folded into this one to run CI once: #815 (promoted widgets on the host), #816 (workflow print), #818 (op shapes, amendment v1.5), #819 (legacy proxyWidgets repair) and the five Langfuse fixes below — 19 commits, every one individually approved by @annehe9 on its own PR (all closed pointing here). Merged with a rebase merge so each commit lands on main with its own message.

Why

A Langfuse sweep of every comfy-agent tool call from 2026-08-25 to 08-28 (1,764 observations, all on comfy-cli pin 8b9c8b01) found 57 failing tool observations in 23 clusters. Most are cleared by #809/#812/#815 or belong to the doc host / agent service (report: langfuse_tool_errors.md, attached to the Linear thread). These five were new and fixable in the CLI, each with the production payload pinned as a red→green test:

fix(cql): accept uploaded files on unmarked input-folder COMBOs (VHS_LoadVideo) (0f2cd990)

comfy run refused every content hash an agent uploaded to Comfy Cloud and
wired into VHS_LoadVideo.video (unknown_enum_value '<64hex>.mp4' not in 1 known options for video) — an eight-shot assembly never rendered. The port
exemption for upload-backed COMBOs only honoured the core loaders'
<kind>_upload marker; VideoHelperSuite lists the input folder itself and
attaches its own upload button by class name, so its catalog entry is a bare
[["bedroom.mp4"]].

When the catalog declares nothing, the listing decides: a COMBO whose every
option is a media file name is a folder listing, not an install-time
vocabulary. An explicit <kind>_upload: false still wins. Model folders
(.safetensors) and plain enums stay constrained. Pinned with the real
cloud catalog entry.

fix(cql): no edge_type_mismatch when the produced type is in a comma-separated accept list (ac701283)

ComfyUI types a multi-type socket as a comma-separated union (INT,FLOAT
on a math operand, MESH,FILE_3D_GLB,… on a 3D importer). The validate edge
check compared raw strings, so a FLOAT output feeding an INT,FLOAT input
warned edge_type_mismatch — 12 of the 23 such warnings in the 08-25..28
prod window were this shape, on edges the frontend draws and the server runs.

Route the check (and the "use X[i] instead" hint) through the converter's
_is_valid_connection, the mirror of the frontend's isValidConnection,
which expands the union on both ends. Wildcards (*, COMFY_MATCHTYPE_V3)
keep short-circuiting first; the report's other four warnings were that
wildcard, which the pin already honours (TestMatchTypeWildcard).

fix(assets): assets library ensure 404 is asset_not_found, not workflow_not_found (016d28c6)

An agent passed a file name (comfyorg_logo.png) where the content hash
belongs; the API answered 404 and the CLI said workflow_not_found /
"workflow not found (ensure)" with a hint to workflow list — the
cloud_http helper's 404 branch hardcodes the saved-workflow vocabulary.

Route ensure through the parameterized _cloud_errors helper with a new
registered asset_not_found code: the message names the hash, details.hash
carries it, and the hint says how to get a real one (assets library ls,
or upload first). The registry test now recognises not_found_code= as a
code kwarg so callers of the parameterized helper register like any other.

fix(workflow): let clear and reset-doc accept an API-format draft (cec8eadb)

generate --emit-workflow leaves an API-format draft in the tab's scratch
file; every edit then fails workflow_not_frontend_format — including
clear, so nothing could replace the draft and the agent abandoned the tab
(Langfuse 2026-08-26).

Clearing discards the content, so the format of what is thrown away is no
reason to refuse. clear and reset-doc now load through a loader that
accepts either format, substituting the empty frontend baseline for an
API-format document so the op applies to a frontend document and the file is
editable afterwards. A JSON value that is a workflow in neither format still
fails as before; the slot-editing commands keep their frontend-only gate.

feat(generate): flag --emit-workflow support per model in generate list (2d63b325)

generate list advertises the whole proxy catalog while --emit-workflow
can render five of them as partner nodes; an agent asked for a workflow for
flux-pro and got the umbrella emit_workflow_failed with the supported
set buried in prose (Langfuse 2026-08-27). It had no way to know beforehand.

Every generate list row now carries emit_supported: bool (schema-required,
generate_list.json), computed by the same lookup build_workflow uses so
the two cannot drift. Asking for --emit-workflow on an unsupported model
raises a typed UnsupportedModelError and surfaces as its own registered
code, emit_workflow_unsupported_model, with details.model and
details.supported as data; emit_workflow_failed is left for bad inputs
and unwritable paths.

Verification

Full suite on this head (the stack head, 7b892b0f): 6379 passed, 38 skipped; public-repo-hygiene green across the stack. 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 21 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: ed3dba92-e7c0-45d7-831d-e41c3c86a009

📥 Commits

Reviewing files that changed from the base of the PR and between 2d63b32 and f1c5973.

📒 Files selected for processing (14)
  • comfy_cli/command/generate/app.py
  • 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
  • docs/op-vocabulary-v1.md
  • tests/comfy_cli/command/test_workflow_edit.py
  • tests/comfy_cli/command/test_workflow_edit_legacy_proxy.py
  • tests/comfy_cli/command/test_workflow_edit_promoted.py
  • tests/comfy_cli/cql/test_promoted_inputs.py
  • tests/comfy_cli/cql/test_proxy_migration.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: c8da7692-78f5-4f64-bd1a-d410fd3cf85f

📥 Commits

Reviewing files that changed from the base of the PR and between 27214b8 and 2d63b32.

📒 Files selected for processing (14)
  • comfy_cli/command/assets_library.py
  • comfy_cli/command/generate/app.py
  • comfy_cli/command/generate/emit.py
  • comfy_cli/command/workflow_edit.py
  • comfy_cli/cql/engine.py
  • comfy_cli/error_codes.py
  • comfy_cli/schemas/generate_list.json
  • tests/comfy_cli/command/generate/test_emit.py
  • tests/comfy_cli/command/generate/test_list_schema_envelope.py
  • tests/comfy_cli/command/test_assets_library.py
  • tests/comfy_cli/command/test_workflow_edit.py
  • tests/comfy_cli/cql/test_engine.py
  • tests/comfy_cli/fixtures/object_info_vhs_loadvideo.json
  • tests/comfy_cli/output/test_error_code_registry.py

Included review availability: 0 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

Changes

Asset error reporting

Layer / File(s) Summary
Asset error contract and validation
comfy_cli/command/assets_library.py, comfy_cli/error_codes.py, tests/comfy_cli/command/test_assets_library.py, tests/comfy_cli/output/test_error_code_registry.py
assets library ensure reports hash-based missing assets with asset_not_found. Tests cover 404, 401, and successful requests. The registry scanner recognizes not_found_code.

Workflow emission model support

Layer / File(s) Summary
Model capability and error contracts
comfy_cli/command/generate/emit.py, comfy_cli/schemas/generate_list.json
Model lookup exposes is_supported. Unsupported models raise UnsupportedModelError. Model records require emit_supported.
CLI reporting and catalog output
comfy_cli/command/generate/app.py, comfy_cli/error_codes.py, tests/comfy_cli/command/generate/test_emit.py, tests/comfy_cli/command/generate/test_list_schema_envelope.py
The CLI returns structured unsupported-model errors and emits capability flags in model listings. Tests verify aliases, canonical IDs, error details, hints, exit status, and output-file behavior.

Destructive workflow editing

Layer / File(s) Summary
Workflow loading and command integration
comfy_cli/command/workflow_edit.py, tests/comfy_cli/command/test_workflow_edit.py
clear and reset-doc accept frontend or API workflow JSON. API documents become empty frontend workflows. Invalid non-workflow JSON remains rejected.

CQL port and edge validation

Layer / File(s) Summary
Upload-backed port detection
comfy_cli/cql/engine.py, tests/comfy_cli/fixtures/object_info_vhs_loadvideo.json, tests/comfy_cli/cql/test_engine.py
CQL detects media-only COMBO listings as upload-backed and preserves explicit upload markers, including false. Tests cover uploaded hashes, media fixtures, and constrained option validation.
Socket compatibility and mismatch validation
comfy_cli/cql/engine.py, tests/comfy_cli/cql/test_engine.py
Edge checks support wildcard and union socket types. Tests retain mismatch warnings for incompatible concrete types and validate compatible replacement hints.

Suggested reviewers: mattmillerai, annehe9

Merge Risk: ⚪ Minimal · up to 2d63b

The PR fixes five localized CLI behaviors and reports passing validation; no actionable merge-blocking risk remains beyond normal checks and review.

🚥 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/prod-tool-errors-aug26
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch kishore/prod-tool-errors-aug26

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

@coderabbitai
coderabbitai Bot requested review from annehe9 and mattmillerai August 28, 2026 08:04
@skishore23
skishore23 force-pushed the kishore/legacy-proxy-repair branch from 2edaec3 to de3d37c Compare August 28, 2026 08:15
@skishore23
skishore23 force-pushed the kishore/prod-tool-errors-aug26 branch from 2d63b32 to 1857f3e 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 force-pushed the kishore/legacy-proxy-repair branch from de3d37c to db071b0 Compare August 28, 2026 17:22
@skishore23
skishore23 force-pushed the kishore/prod-tool-errors-aug26 branch from 1857f3e to 145161f Compare August 28, 2026 17:22
@dosubot dosubot Bot added the lgtm This PR has been approved by a maintainer label Aug 28, 2026
@skishore23
skishore23 force-pushed the kishore/legacy-proxy-repair branch from db071b0 to c42579a Compare August 28, 2026 23:41
@skishore23
skishore23 force-pushed the kishore/prod-tool-errors-aug26 branch from 145161f to 867a57e Compare August 28, 2026 23:41
@skishore23
skishore23 force-pushed the kishore/legacy-proxy-repair branch from 905070f to aea6766 Compare August 28, 2026 23:50
@skishore23
skishore23 force-pushed the kishore/prod-tool-errors-aug26 branch from 867a57e to a558702 Compare August 28, 2026 23:50
@skishore23
skishore23 force-pushed the kishore/legacy-proxy-repair branch from aea6766 to 3505c85 Compare August 29, 2026 00:59
@skishore23
skishore23 force-pushed the kishore/prod-tool-errors-aug26 branch from a558702 to 7b892b0 Compare August 29, 2026 00:59
@skishore23
skishore23 force-pushed the kishore/legacy-proxy-repair branch from 3505c85 to 3fa7a52 Compare August 29, 2026 01:42
@skishore23
skishore23 force-pushed the kishore/prod-tool-errors-aug26 branch from 7b892b0 to 68a5637 Compare August 29, 2026 01:42
@skishore23
skishore23 force-pushed the kishore/legacy-proxy-repair branch from 3fa7a52 to 0abfda8 Compare August 29, 2026 01:54
@skishore23
skishore23 force-pushed the kishore/prod-tool-errors-aug26 branch from 68a5637 to 57faebb Compare August 29, 2026 01:54
…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>
skishore23 and others added 18 commits August 28, 2026 18:55
…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>
…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>
…onal payload for PrimitiveNode writes (amendment v1.5)

The comfy-multi-player applier (PR #104) implemented the host-write and
promoted-connect op shapes #815 introduced and sent two convergence asks
back:

* A promoted subgraph input is ONE register named by the definition, but
  `_apply_connect` did not gate it: two concurrent connects onto `57.width`
  left the entry with whichever link arrived first, and `grow_id` stayed on
  the first arrival. The grow is now gated by `_lww_gate`/`_lww_commit` on
  `("input", to_node, "grow", <full name>)` exactly like a concrete input
  (§11.1): the higher stamp owns the entry in either apply order, `grow_id`
  follows the winner and the loser's link is retired. The register uses the
  full declared name — sg input names such as `images.image0` contain a
  dot, and splitting on it would alias two inputs onto one register.

* A frontend-only PrimitiveNode has no catalog entry, so an opaque store
  rejected the `legacy_primitive` write it had no positional payload for.
  The op now carries the same `promoted` payload a host write carries
  (`value_index: 0`, `instance_path`, `host_widgets_values`), and apply
  treats a `promoted` payload on a non-instance node as a plain positional
  write.

docs/op-vocabulary-v1.md gains Amendment v1.5, which also records the
shapes #815 introduced without a doc amendment (`promoted` host writes,
`redirected_from`, `grow.promoted`).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…extend truncated positional arrays from the payload

Review findings on #818. A later connect to an already-materialized
promoted input minted a concrete `to_slot` op on the concrete register: a
replica receiving it before the materializing op found no slot, dropped it
with its op_id consumed, and then installed the older link — the newer link
could never replay. Every connect to a declared promoted input is now the
promoted grow (apply reuses the entry by name), so the register is the
declared name for the life of the input. `_apply_positional_write` extends a
truncated opaque array from `host_widgets_values` whether or not the written
index is inside it, so replicas converge on the node's whole state. Fences in
Amendment v1.5 carry a language (MD040).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ed bail; index addresses share the name register

Review (annehe9) on #818:

* The grow branch still opened with the pre-v1.5 "source deleted → return",
  so a promoted connect whose source was concurrently deleted never reached
  the gate: two promoted connects into 57.width plus a delete of the higher
  op's source gave two canonical states across the six orders. The bail now
  applies to autogrow only; a promoted grow gates, claims, retires the
  loser's link and leaves the entry empty at the existing `src is None`
  check — delete wins over the link, not over the claim (§11.1), and all six
  orders converge.
* `57.<index>` landing on the materialized `width` entry minted a concrete
  `to_slot` op on the concrete register, so it never gated against `57.width`.
  An index that resolves to a declared promoted input now maps onto the name
  at mint time; both addresses share `("input", 57, "grow", "width")`.
* Amendment v1.5: the tail extension of a truncated positional array is
  best-effort repair (only the written index is under the register); index
  addressing and the pre-v1.5 concrete-op scope are stated explicitly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…hem, the way the frontend does on load (BE-10305)

A `properties.proxyWidgets` entry the subgraph definition does not back
with a linked input used to route `set-widget`/`set-slot`/`vary` into the
interior node — an edit under layer 2 that the frontend's load-time
migration re-seeds the host from (so it looked right) but that no other
promoted write shares and that `comfy run` never submitted as a host value.

`cql.promoted` now carries a port of the frontend's forward migration
(`proxyWidgetMigration.ts`, ADR 0009): `plan_proxy_migration` classifies
every entry of an instance exactly as `classify` does (already linked;
`createSubgraphInput`; `PrimitiveNode` fan-out bypass, all-or-quarantine;
`$$`/preview pseudo-widget exposure; quarantine with the frontend's reason
codes), and `flush_proxy_migration` performs it: a subgraph input named with
`nextUniqueName`, a boundary link from the subgraph input node into the
widget's backing input slot (synthesized in the shape the frontend
serializes when an older save omits it), the host value read positionally
by legacy proxy order, quarantine rows `{originalEntry, reason, hostValue?,
attemptedAtVersion: 1}`, repaired entries consumed. Preview entries are left
in `proxyWidgets` so the frontend's own preview-exposure migration (and its
auto-exposure of preview nodes) produces the state it would have produced
from the untouched file.

`resolve_write` turns such an entry — addressed flat, by its legacy widget
name, or through the interior widget/primitive target it owns — into a HOST
target carrying the repair; the op records `promoted.repair = {entry, ids}`
with input/link ids derived by SHA-256 from (instance path, source node,
widget), never random, so replay on another replica is byte-identical and
concurrent repairs of one instance converge. A shared definition is forked
first, like an interior write. Entries the migration quarantines
(`control_after_generate` has no backing slot) keep the interior widget as
the live one, so those writes land there as before.

Reads present a pending repair where the frontend will show it:
`slots`/`effective_value` advertise `<instance>.<name>` with the legacy host
value or the interior source, and hide the interior address; reads never
mutate. The converter now overlays a host value onto EVERY boundary-link
target, which a repaired primitive fan-out needs.

Fixtures: three verbatim gallery templates (primitive fan-outs under user
titles, `$$canvas-image-preview` exposures next to a value widget with no
serialized input slot, and the common `seed` + `control_after_generate`
pair) with a trimmed cloud catalog. `TestSetWidgetSubgraph` is rewritten for
the migration semantics on a realistic legacy fixture, with the old
declared-but-unlinked shape pinned separately (`nextUniqueName` mints
`text_1` beside the dangling socket).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ecting input; guard the primitive link scan (PR #819 review)

A legacy proxyWidgets entry whose source is a nested subgraph instance is
backed by that instance's own host input for the PROJECTING subgraph input
(`getSlotFromWidget(promotedInputWidget(input))`). When the instance
serializes no such slot, the repair synthesized one named after the interior
widget instead — a name the inner definition cannot resolve when the two
differ, so the outer promotion never reached the concrete widget. The
synthesized slot now carries the projecting input's name (the shape a
subgraph instance serializes), pinned on a fixture where the names differ.

The primitive-bypass link scan now screens non-dict link entries like every
other scan in the module. Also pins the `<id>:widget` legacy prefix rule
(`normalizeLegacyProxyWidgetEntry`) and the disambiguator match through a
nested instance.

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

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… legacy alias, document link replacement (PR #819 review)

An interior input slot an older save serializes by `name` alone (no `widget`
marker) is the widget's backing slot: the repair now matches it by name and
restores the marker instead of appending a duplicate entry beside it.

The legacy-widget-name alias (`143.value`) now raises when more than one
entry shares that widget name, listing the minted input addresses, instead
of silently taking the first match.

A slot already fed by an interior link is repaired as before — the frontend
calls `SubgraphInput.connect` unconditionally and `connect` replaces the
incumbent link (`replaceLinkTopology` + `_disconnectNodeInput`) — now
pinned by a test and explained where `resolve_write` deliberately redirects
before the interior-feeder refusal. `missingSubgraphInput` for a widget with
no backing slot is the frontend's own reason code; commented as such.

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

`comfy run` refused every content hash an agent uploaded to Comfy Cloud and
wired into `VHS_LoadVideo.video` (`unknown_enum_value '<64hex>.mp4' not in 1
known options for video`) — an eight-shot assembly never rendered. The port
exemption for upload-backed COMBOs only honoured the core loaders'
`<kind>_upload` marker; VideoHelperSuite lists the input folder itself and
attaches its own upload button by class name, so its catalog entry is a bare
`[["bedroom.mp4"]]`.

When the catalog declares nothing, the listing decides: a COMBO whose every
option is a media file name is a folder listing, not an install-time
vocabulary. An explicit `<kind>_upload: false` still wins. Model folders
(`.safetensors`) and plain enums stay constrained. Pinned with the real
cloud catalog entry.

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

ComfyUI types a multi-type socket as a comma-separated union (`INT,FLOAT`
on a math operand, `MESH,FILE_3D_GLB,…` on a 3D importer). The validate edge
check compared raw strings, so a `FLOAT` output feeding an `INT,FLOAT` input
warned `edge_type_mismatch` — 12 of the 23 such warnings in the 08-25..28
prod window were this shape, on edges the frontend draws and the server runs.

Route the check (and the "use X[i] instead" hint) through the converter's
`_is_valid_connection`, the mirror of the frontend's `isValidConnection`,
which expands the union on both ends. Wildcards (`*`, `COMFY_MATCHTYPE_V3`)
keep short-circuiting first; the report's other four warnings were that
wildcard, which the pin already honours (TestMatchTypeWildcard).

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

An agent passed a file name (`comfyorg_logo.png`) where the content hash
belongs; the API answered 404 and the CLI said `workflow_not_found` /
"workflow not found (ensure)" with a hint to `workflow list` — the
`cloud_http` helper's 404 branch hardcodes the saved-workflow vocabulary.

Route `ensure` through the parameterized `_cloud_errors` helper with a new
registered `asset_not_found` code: the message names the hash, `details.hash`
carries it, and the hint says how to get a real one (`assets library ls`,
or upload first). The registry test now recognises `not_found_code=` as a
code kwarg so callers of the parameterized helper register like any other.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
`generate --emit-workflow` leaves an API-format draft in the tab's scratch
file; every edit then fails `workflow_not_frontend_format` — including
`clear`, so nothing could replace the draft and the agent abandoned the tab
(Langfuse 2026-08-26).

Clearing discards the content, so the format of what is thrown away is no
reason to refuse. `clear` and `reset-doc` now load through a loader that
accepts either format, substituting the empty frontend baseline for an
API-format document so the op applies to a frontend document and the file is
editable afterwards. A JSON value that is a workflow in neither format still
fails as before; the slot-editing commands keep their frontend-only gate.

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

`generate list` advertises the whole proxy catalog while `--emit-workflow`
can render five of them as partner nodes; an agent asked for a workflow for
`flux-pro` and got the umbrella `emit_workflow_failed` with the supported
set buried in prose (Langfuse 2026-08-27). It had no way to know beforehand.

Every `generate list` row now carries `emit_supported: bool` (schema-required,
`generate_list.json`), computed by the same lookup `build_workflow` uses so
the two cannot drift. Asking for `--emit-workflow` on an unsupported model
raises a typed `UnsupportedModelError` and surfaces as its own registered
code, `emit_workflow_unsupported_model`, with `details.model` and
`details.supported` as data; `emit_workflow_failed` is left for bad inputs
and unwritable paths.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@skishore23
skishore23 force-pushed the kishore/legacy-proxy-repair branch from 0abfda8 to 813c7cd Compare August 29, 2026 02:03
@skishore23
skishore23 force-pushed the kishore/prod-tool-errors-aug26 branch from 57faebb to f1c5973 Compare August 29, 2026 02:03
@skishore23
skishore23 changed the base branch from kishore/legacy-proxy-repair to main August 29, 2026 02:03
@dosubot dosubot Bot removed the size:L This PR changes 100-499 lines, ignoring generated files. label Aug 29, 2026
@dosubot dosubot Bot added the size:XXL This PR changes 1000+ lines, ignoring generated files. label Aug 29, 2026
@skishore23
skishore23 merged commit ba0b0b9 into main Aug 29, 2026
23 checks passed
@skishore23
skishore23 deleted the kishore/prod-tool-errors-aug26 branch August 29, 2026 02:26
@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

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