SIGN IN SIGN UP

fix: rebuild drifted built-in components instead of refusing the flow (#14527)

* fix: rebuild drifted built-ins instead of refusing the flow

With LANGFLOW_ALLOW_CUSTOM_COMPONENTS=false the code stored in a node never
runs: resolve_trusted_code_for_build substitutes this server's copy, keyed by
code hash, before exec. check_flow_and_raise nevertheless refused the whole
flow when a node's stored code hash did not match the current template, so any
upgrade that touched a built-in blocked every saved flow using it until each
node was updated by hand. The unauthenticated public build path already avoids
this: prepare_public_flow_build swaps in the server's code by component type
and refuses only unrecognized types.

Apply the same swap-by-type rule on the normal build path, behind
LANGFLOW_SUBSTITUTE_OUTDATED_COMPONENT_CODE (default true, no effect while
custom components are allowed). Graph.from_payload rewrites recognized
built-ins whose stored code has drifted, right after the extension migration
pass and before validation, so check_flow_and_raise passes on its own and
unrecognized types still reach it and are still blocked. Pre-check call sites
that do not own the payload agree about which types are substitutable rather
than refusing a build that would then succeed.

Nothing new becomes runnable: the code that executes is always this server's
own, selected by component type. Every swap is logged, and the stored flow is
left untouched so the editor keeps flagging the node as outdated.

Fixes #14455

* fix(lfx): never substitute a component type two components claim

A type alias can be contributed by two different components -- a built-in and a
components_path component of the same name, or an XComponent/X pair across
bundles. collect_component_hash_lookups keeps every such hash, but
collect_component_code_lookups is first-wins by registry iteration order, so its
single code value may belong to either one. Stored code matching neither hash is
then indistinguishable from "this node is the other component", and the
substitution would rebuild the node as the wrong component.

Gate the swap on the type resolving to exactly one current hash, expressed once
as SubstitutableComponentTypes and shared by both sides: the substitution pass in
from_payload and the pre-check in validate_flow_for_current_settings. Sharing the
rule is what keeps them in agreement -- exempting an ambiguous type from the
outdated check without substituting it would let the node clear validation
carrying code resolve_trusted_code_for_build then fails on, with a far less
actionable message. Ambiguous types are now neither substituted nor exempted, so
they stay blocked as outdated exactly as before this pass existed.

Also assert the endpoint test's substitution actually fires, via a spy on the
real run, instead of only asserting the 200 it would return anyway once the
fixture's stored code caught up with the server's.

Both found by CodeRabbit on #14527.

* fix: align trusted component substitution policies

* fix: validate final public component source

* test: isolate live provider checks

* test: classify remaining live service checks

* test: keep comet client construction local
E
Eric Hare committed
5c80b497886951b9fe9bba1b3d7d45a15b64130b
Parent: 7b35e88
Committed by GitHub <noreply@github.com> on 8/13/2026, 8:13:27 PM