Skip to content

fix(graph): reject mismatched collector items for union collection inputs - #9853

Open
unmarco wants to merge 2 commits into
invoke-ai:mainfrom
unmarco:fix/collector-union-output-types
Open

unmarco wants to merge 2 commits into
invoke-ai:mainfrom
unmarco:fix/collector-union-output-types

Conversation

@unmarco

@unmarco unmarco commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Summary

A Collect node connected to an input typed Union[list[X], X] or Optional[list[X]] with mismatched items crashed graph validation with TypeError: issubclass() argument 2 cannot be a parameterized generic, so enqueue returned 500 (which webv2 retries indefinitely) instead of 422.

Collector output validation now reads the destination's item types with the existing extract_collection_item_types(), which handles list branches inside unions, and only calls issubclass() on real classes. Mismatched items are rejected with the usual "matching type" error; matching items are accepted as before.

Before: integer, integer → collect → metadata.items → TypeError → HTTP 500
After: InvalidEdgeError: Collector outputs must connect to a collection input with a matching type → HTTP 422

Related Issues / Discussions

Closes #9852

QA Instructions

  • uv run --no-sync pytest tests/test_node_graph.py -k "union_input or optional_list_input or list_first_union": the two new rejection cases fail on main with the TypeError and pass with this change; the two acceptance cases pass on both.
  • uv run --no-sync pytest tests/test_node_graph.py tests/test_graph_execution_state.py tests/app/services/shared tests/app/services/test_workflow_graph_builder.py tests/app/routers/test_session_queue_sanitization.py -n 4: 1753 passed, 22 skipped, 2 xfailed.
  • uv tool run ruff@0.11.2 check / format --check on the changed files: clean.
  • Batch.model_validate on the repro graph from the issue: ValidationError (422) instead of TypeError (500). Also checked merge_metadata.collection and core_metadata.loras: both now raise InvalidEdgeError.
  • Not run: the full pytest -n logical suite, and no browser check (no UI change).

Review

No blocking findings. Intended behaviour changes beyond the crash, all toward reading the annotation correctly:

  • Union[int, list[str]] with integer items was accepted, because the scalar happened to come first. It is now rejected.
  • Union[list[A], list[B]] matches if any list branch matches, not only the first.
  • Optional[list[Any]] / Union[list[Any], X] accept collected items, typed or untyped, as list[Any] already did. Before, they crashed or were rejected. No core node uses such an annotation today.

Checklist

  • The PR has a short but descriptive title, suitable for a changelog
  • Meaningful regression coverage added / updated where needed; obsolete tests/code removed
  • Persisted-state and API changes include required migrations / compatibility validation
  • Relevant performance/efficiency opportunities considered; material claims have evidence
  • Material review findings resolved and relevant checks rerun
  • Documentation added / updated (if applicable)
  • Updated What's New copy (if doing a release after this PR)

View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

…puts

Collector output validation took the first type argument of the destination as its item
type, so Union[list[X], X] and Optional[list[X]] inputs raised a TypeError on mismatched
items and enqueue returned 500. Read item types from the list branches instead.

@JPPhoto JPPhoto left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Hi @unmarco, thanks for the PR.

In my review, I uncovered this:

  • invokeai/app/services/shared/graph_validation.py:2340: For a collector of Derived(Base) targeting list[Union[Base, str]], the union-aware subtype check misses the subclass, and the class guard skips issubclass; validation rejects a valid edge. The base accepted it. Effect: valid workflows cannot connect this collector. Likelihood: plausible when a node declares union-typed list items. Recovery: manual graph or type workaround. Test: the candidate validator reproduces the rejection; add a Graph.add_edge regression for this case.

Please add the test and fix the code and perform another self-review, then re-request review from me when complete.

A collection input like list[Union[Base, str]] now matches a collector of
Derived(Base) again: the class guard checks issubclass against each union
member. Adds Graph.add_edge regressions for typing.Union and PEP 604 unions.
@unmarco

unmarco commented Oct 17, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for catching this. Fixed in 796b0f8.

_collector_item_matches now checks issubclass against each member when the item type is a union (typing.Union or PEP 604 X | Y). A Derived(Base) collector is accepted by list[Union[Base, str]] again, and mismatched items are still rejected.

I added Graph.add_edge regressions for both the accept and reject cases, each run with both union spellings. On the previous commit, both accept cases fail.

Self-review and checks:

  • tests/test_node_graph.py and tests/test_nodes.py pass (158 tests), and ruff check / ruff format --check are clean.
  • The graph engine, session queue and shared-service suites pass (1697 passed, 15 skipped, 2 xfailed).
  • End to end, the original repro (Integer ×2 → Collect → Metadata items) now returns 422 from enqueue_batch instead of the 500 on 7.0.0-alpha.2. A valid MetadataItem ×2 → Collect → Metadata graph still enqueues and completes.

@unmarco
unmarco requested a review from JPPhoto October 17, 2026 08:35

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

python PRs that change python files python-tests PRs that change python tests services PRs that change app services

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[bug]: Enqueue returns 500 when a Collect node feeds a Union[list[X], X] / Optional[list[X]] input with the wrong item type

2 participants