You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A Merge Flow node may signal "done" during Play All before it has propagated its merged output to downstream nodes, so a downstream node can execute with stale/empty input (arg = None).
On the datacatalog branch the merge behavior returns disablePlay: true. In UniversalNode, the Play-All triggerExec handler short-circuits on disablePlay — it calls signalNodeExecDone(...) and returns before invoking the merge's sendCodeOverride (the synchronous emitter that pushes the merged output downstream). signalNodeExecDone then synchronously advances the run to the next level (triggerLevel), bumping the downstream node's triggerExec. The merge's output now only reaches downstream data.input via its reactive useEffect → outputCallback → propagateDownstreamInputs, which commits on a later React render — potentially after the downstream node has already executed.
In main, the merge behavior exposed sendCodeOverridewithoutdisablePlay, so the merge emitted synchronously on the Play-All path and signalNodeExecDone fired only after propagation (the same pattern Data Pool still uses).
Note: confirmed as a real mechanism by code reading, but the exact React render/commit/effect interleaving has not been reproduced at runtime — marked Discussion required so it can be verified before/while fixing.
Steps to Reproduce
Wire two source nodes into a Merge Flow node.
Wire a downstream node (e.g. a Python/JS transform) to the merge's output.
Click Play All (rather than running nodes individually).
Inspect the input the downstream node receives on that run.
Expected Result
The downstream node receives the merge's merged output as its input on the Play-All run — the merge propagates its output before the run advances to the downstream level.
Actual Result
The merge short-circuits (disablePlay → signalNodeExecDone + early return), the run advances to the downstream node before the merge's reactive propagation commits, and the downstream node can execute with stale/empty input (arg = None). Running the downstream node manually afterwards works, because by then propagation has committed.
utk_curio/frontend/urban-workflows/src/components/UniversalNode.tsx — if (disablePlay || !sendCode) { signalNodeExecDone(...); return; } runs before sendCode on the triggerExec path.
utk_curio/frontend/urban-workflows/src/providers/FlowProvider.tsx — signalNodeExecDone → triggerLevel advances synchronously; applyNewOutput → propagateDownstreamInputs is the propagation path.
Reference pattern: utk_curio/frontend/urban-workflows/src/adapters/node/dataPoolBehavior.tsx exposes sendCodeOverride and does not set disablePlay.
Suggested direction: drop disablePlay: true from the merge behavior and rely on sendCodeOverride to emit synchronously during Play All (so signalNodeExecDone fires after propagation), matching Data Pool.
Summary
A Merge Flow node may signal "done" during Play All before it has propagated its merged output to downstream nodes, so a downstream node can execute with stale/empty input (
arg = None).On the
datacatalogbranch the merge behavior returnsdisablePlay: true. InUniversalNode, the Play-AlltriggerExechandler short-circuits ondisablePlay— it callssignalNodeExecDone(...)andreturns before invoking the merge'ssendCodeOverride(the synchronous emitter that pushes the merged output downstream).signalNodeExecDonethen synchronously advances the run to the next level (triggerLevel), bumping the downstream node'striggerExec. The merge's output now only reaches downstreamdata.inputvia its reactiveuseEffect→outputCallback→propagateDownstreamInputs, which commits on a later React render — potentially after the downstream node has already executed.In
main, the merge behavior exposedsendCodeOverridewithoutdisablePlay, so the merge emitted synchronously on the Play-All path andsignalNodeExecDonefired only after propagation (the same pattern Data Pool still uses).Steps to Reproduce
Expected Result
The downstream node receives the merge's merged output as its input on the Play-All run — the merge propagates its output before the run advances to the downstream level.
Actual Result
The merge short-circuits (
disablePlay→signalNodeExecDone+ early return), the run advances to the downstream node before the merge's reactive propagation commits, and the downstream node can execute with stale/empty input (arg = None). Running the downstream node manually afterwards works, because by then propagation has committed.Environment
datacatalog(b6979cb)urban-workflows(local dev), Chromium-based browserAdditional Information
utk_curio/frontend/urban-workflows/src/adapters/node/mergeFlowBehavior.ts— return block addsdisablePlay: true(not present inmain);sendCodeOverride/ reactive emit effect.utk_curio/frontend/urban-workflows/src/components/UniversalNode.tsx—if (disablePlay || !sendCode) { signalNodeExecDone(...); return; }runs beforesendCodeon thetriggerExecpath.utk_curio/frontend/urban-workflows/src/providers/FlowProvider.tsx—signalNodeExecDone→triggerLeveladvances synchronously;applyNewOutput→propagateDownstreamInputsis the propagation path.utk_curio/frontend/urban-workflows/src/adapters/node/dataPoolBehavior.tsxexposessendCodeOverrideand does not setdisablePlay.disablePlay: truefrom the merge behavior and rely onsendCodeOverrideto emit synchronously during Play All (sosignalNodeExecDonefires after propagation), matching Data Pool.