fix(condition): stop shipping all block outputs in every evaluation - #6468
Conversation
ConditionBlockHandler forwarded collectBlockData's full blockData — every
block output accumulated so far in the run — to function_execute on each
condition evaluation. The resolver already inlines every <block.field>
reference into the expression before the handler runs, so that payload was
never read; it only inflated the request body.
Inside a wide subflow one flat blockStates map holds every branch's outputs,
so a 91-branch parallel pushed the body past the 10MB cap and failed the
gate with "Request body size limit exceeded" even though the expression was
just a boolean compare. Per-value large-value offload does not catch this:
its threshold is 8MB for a single value, while this is an aggregate of many
medium ones.
Mirrors FunctionBlockHandler, which moved to blockData: {} in #4560 and left
the condition handler on the old path.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
PR SummaryMedium Risk Overview This fixes wide parallel/subflow runs where each gate re-sent every branch’s outputs and could exceed the 10MB request body limit, even for simple expressions after the resolver had already inlined block references. Tests expect empty Reviewed by Cursor Bugbot for commit 93fd34f. Configure here. |
Greptile SummaryThe PR stops condition evaluations from forwarding accumulated block outputs after condition references have already been resolved, preventing request bodies from growing with run state.
Confidence Score: 5/5The PR appears safe to merge with no actionable correctness or security issues identified. Condition inputs are resolved before handler execution, while unresolved references could not have been rescued by the old payload because both paths derive outputs from the same execution state.
|
| Filename | Overview |
|---|---|
| apps/sim/executor/handlers/condition/condition-handler.ts | Replaces accumulated runtime block outputs with an empty payload after executor-side input resolution; no actionable regression was established. |
| apps/sim/executor/handlers/condition/condition-handler.test.ts | Updates request assertions and adds focused regression coverage ensuring collected block outputs are omitted. |
Sequence Diagram
sequenceDiagram
participant Executor as VariableResolver
participant Handler as ConditionBlockHandler
participant Function as function_execute
Executor->>Executor: Resolve condition references
Executor->>Handler: Resolved expression
Handler->>Function: "code + blockData {}"
Function-->>Handler: Boolean result
Reviews (1): Last reviewed commit: "fix(condition): stop shipping all block ..." | Re-trigger Greptile
Problem
ConditionBlockHandlerforwardscollectBlockData's fullblockData— every block output accumulated so far in the run — tofunction_executeon each condition evaluation.That payload is never read. The resolver already inlines every
<block.field>reference into the expression before the handler runs (resolver.tsresolveInputs→resolveTemplateWithoutConditionFormatting), so by the time the handler builds the request there is nothing left for the server-sideresolveTagVariablespass to resolve. It only inflates the request body.Inside a wide subflow this is fatal: one flat
blockStatesmap holds every branch's outputs, so branch N's gate carries branches 0…N. A 91-branch parallel pushed the body past the 10MB cap and failed with:…even though the expression was just
<someflag.result> === true.Per-value large-value offload does not catch this —
LARGE_VALUE_THRESHOLD_BYTESis 8MB for a single value, while this is an aggregate of many medium ones, so nothing ever trips the offload path.Fix
Send
blockData: {}, mirroringFunctionBlockHandler, which moved to exactly this in #4560 and left the condition handler on the old path.blockNameMappingandblockOutputSchemasare kept — they're bounded by workflow size, not run data.Why this is behavior-preserving
blockDatahad exactly one server-side consumer:resolveTagVariables(app/api/function/execute/route.ts), which resolves leftover<...>tags. So this can only regress if a<...>tag survives the resolver into a condition's code.It can't:
BlockResolvercalls the sameresolveBlockReferencethe route calls — both import from@/executor/utils/block-reference. The server pass was running identical logic over the same data, one step later.findBlockIdByNameisnameToBlockId.get(normalizeName(name))built fromworkflow.blocks;collectBlockData'sblockNameMappingis built from the same array with the samenormalizeName. Identical key set, so "executor can't find it" ⟹ "server can't find it."getBlockOutputis strictly richer (outer-branch index,parallelBlockMapping, cloned-subflow ids, suffix-walking instate.ts).collectBlockDataonly does a simple base-id alias.<loop.*>/<parallel.*>/<variable.*>are handled by dedicated resolvers (SPECIAL_REFERENCE_PREFIXES) and were never inblockNameMappinganyway.Verified against the real resolver — every reference is inlined before the handler:
The only case where a tag survives is an unknown block name, and feeding that case the exact
blockData+blockNameMappingthe old handler shipped returnsundefinedtoo — the old path couldn't rescue it either.Testing
executor/suite: 1843/1843 passbiome check: cleantsc --noEmit: no errors in changed codeblockData; the assertion pins the forwarded value to{})🤖 Generated with Claude Code