Snippet Variable Handling#
Snippets are reusable sub-graphs of workflow nodes. Their variable handling has three distinct concerns: ID remapping at insertion time, runtime binding via virtual start nodes and aliases, and historical regression bugs in v1.15.0βv1.16.0 where variable selectors broke after insertion.
1. Node ID Remapping at Insertion#
When a snippet is inserted into a workflow, every node in the snippet gets a fresh ID to avoid collisions with existing nodes . The ID mapping is applied to:
node.idβ the node's own identityparentIdβ for nested/container nodes_children[].nodeIdβ child metadata within Iteration/Loop containers- All edge
sourceandtargetfields iteration_idβ on nodes within iteration blocksloop_idβ on nodes within loop blocksstart_node_idβ on iteration and loop nodesoutput_selectorβ on iteration nodes- Variable selectors embedded in node data fields β the
ValueSelectorarrays that reference{{#<node_id>.<var>#}}inside Code node argument configs, LLM prompt variables, or HTTP Request URL fields are traversed and remapped viaremapSnippetNodeVariableReferences, which usesgetNodeUsedVars()to find all selectors andupdateNodeVars()to rewrite them with the new node IDs. Assigner nodes (type:assigner) require specialized remapping because their variable selectors are stored in a different schema not covered by the generic traversal β see Assigner Node Variable Remapping below
See remapSnippetGraph and useInsertSnippet for the full insertion logic.
Assigner Node Variable Remapping#
Assigner nodes store variable selectors in a distinct schema not handled by getNodeUsedVars() / updateNodeVars(). Without dedicated remapping, inserted snippets would reference the original template node IDs, causing "missing variable" errors at runtime.
remapSnippetAssignerReferences handles both current (v2) and legacy (v1) assigner schemas:
- v2 assigners β each object in
items[]contains:variable_selectorβ the assignment target (always remapped)variable_inputβ the source value selector (remapped only wheninput_type === 'variable'; constants and literals are preserved)- Missing or empty
itemsarrays are treated as[]to avoid errors
- Legacy assigners β flat structure with:
assigned_variable_selectorβ the target variableinput_variable_selectorβ the source selector (remapped whenwrite_mode === 'over-write')
Only selectors whose first element exists in the snippet ID mapping are remapped. External references (['env', 'api_key']), conversation variables (['conversation', 'count']), and constant values are left unchanged. This preserves cross-boundary references while ensuring internal snippet nodes resolve correctly after insertion.
2. Runtime Variable Binding: Virtual Start Node and Aliases#
Snippet workflows don't persist a real canvas Start node on their graph. At runtime, the backend injects a virtual start node with the internal ID __snippet_virtual_start__ . Because the UI-facing public selector form is {{#start.<var>#}}, the backend registers "start" as a legacy alias so that both selector shapes resolve correctly.
The alias mechanism is implemented in two parts:
get_compatible_start_aliases()β returns("start",)only whenworkflow_kind == "snippet"androot_node_id == "__snippet_virtual_start__", empty tuple otherwise.add_node_inputs_to_pool()invariable_pool_initializer.pyβ iterates both the primary node ID and all aliases, callingvariable_pool.add((node_id, key), value)for each. This duplicates inputs in theVariablePoolunder every registered identifier, so#start.my_var#and#__snippet_virtual_start__.my_var#resolve identically.
The registration happens in WorkflowAppRunner.generate() at runtime, not at draft-save time.
On the frontend, snippet workflows on the /snippets/[id]/orchestrate canvas inject a synthetic Start-like node into the variable picker via appendSnippetInputFieldVars(), using the fixed node ID "start" so that the UI variable selector and the backend resolver agree.
3. Known Bugs#
Selector remapping gap after insertion (RESOLVED in PR #39843)#
Issue #39842
When a snippet containing cross-node variable references was inserted into a workflow, the remapped Code node's input arguments still referenced the old (pre-insertion) LLM node ID. The variable map appeared missing or broken at runtime. The old-to-new ID mapping existed in remapSnippetGraph but was not applied to variable selectors within node data configurations.
Resolved: PR #39843 added the missing deep node data traversal. The fix includes:
remapSnippetNodeVariableReferencesβ traverses all variable selectors in each node usinggetNodeUsedVars()and remaps them withupdateNodeVars()remapSnippetNodeStructuralReferencesβ remaps structural references includingiteration_id,loop_id,start_node_id, andoutput_selector- Both functions are called in
remapSnippetGraphafter node ID regeneration
Variable resolution failure in v1.15.0 (HTTP, LLM nodes)#
Variables from upstream Code or Template nodes fail to resolve in downstream HTTP Request and LLM nodes. The %7B%7B URL-encoding in HTTP logs confirms the literal template string reaches the executor without substitution. This is a broader regression affecting any node using the {{#node_id.var#}} mechanism in v1.15.0. Unresolved as of the reporting date; workaround is to insert an intermediate Template Transform node or verify the exact variable selector path.
Key Source Files#
| File | Purpose |
|---|---|
web/.../use-insert-snippet.ts | Snippet insertion: ID remapping, boundary edges, container placement |
web/.../snippet-input-field-vars.ts | Frontend virtual Start node for snippet canvas variable picker |
api/core/workflow/snippet_start.py | Backend constants and get_compatible_start_aliases() |
api/core/workflow/variable_pool_initializer.py | add_node_inputs_to_pool() β multi-alias variable pool registration |
api/core/workflow/generator/runner.py | Workflow generator/runner |