Skip to content

fix(condition): stop shipping all block outputs in every evaluation - #6468

Merged
icecrasher321 merged 1 commit into
stagingfrom
fix/condition-payload-size
Aug 9, 2026
Merged

fix(condition): stop shipping all block outputs in every evaluation#6468
icecrasher321 merged 1 commit into
stagingfrom
fix/condition-payload-size

Conversation

@icecrasher321

Copy link
Copy Markdown
Collaborator

Problem

ConditionBlockHandler forwards collectBlockData's full blockDataevery block output accumulated so far in the run — to function_execute on each condition evaluation.

That payload is never read. The resolver already inlines every <block.field> reference into the expression before the handler runs (resolver.ts resolveInputsresolveTemplateWithoutConditionFormatting), so by the time the handler builds the request there is nothing left for the server-side resolveTagVariables pass to resolve. It only inflates the request body.

Inside a wide subflow this is fatal: one flat blockStates map 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:

Evaluation error in condition "If": Request body size limit exceeded (10MB).

…even though the expression was just <someflag.result> === true.

Per-value large-value offload does not catch this — LARGE_VALUE_THRESHOLD_BYTES is 8MB for a single value, while this is an aggregate of many medium ones, so nothing ever trips the offload path.

Fix

Send blockData: {}, mirroring FunctionBlockHandler, which moved to exactly this in #4560 and left the condition handler on the old path. blockNameMapping and blockOutputSchemas are kept — they're bounded by workflow size, not run data.

Why this is behavior-preserving

blockData had 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:

  • BlockResolver calls the same resolveBlockReference the route calls — both import from @/executor/utils/block-reference. The server pass was running identical logic over the same data, one step later.
  • Name lookupfindBlockIdByName is nameToBlockId.get(normalizeName(name)) built from workflow.blocks; collectBlockData's blockNameMapping is built from the same array with the same normalizeName. Identical key set, so "executor can't find it" ⟹ "server can't find it."
  • Output lookup — the executor's getBlockOutput is strictly richer (outer-branch index, parallelBlockMapping, cloned-subflow ids, suffix-walking in state.ts). collectBlockData only does a simple base-id alias.
  • <loop.*> / <parallel.*> / <variable.*> are handled by dedicated resolvers (SPECIAL_REFERENCE_PREFIXES) and were never in blockNameMapping anyway.

Verified against the real resolver — every reference is inlined before the handler:

<producer.result> === 'hello world'   →   'hello world' === 'hello world'
<producer.items>.length > 1           →   ["a","b"].length > 1
<neverran.result> === true            →   null === true      ← block never executed
<variable.thr> > 3                    →   5 > 3

The only case where a tag survives is an unknown block name, and feeding that case the exact blockData + blockNameMapping the old handler shipped returns undefined too — the old path couldn't rescue it either.

Testing

  • executor/ suite: 1843/1843 pass
  • biome check: clean
  • tsc --noEmit: no errors in changed code
  • Added a regression test asserting collected block outputs are never forwarded (the mock returns a non-empty blockData; the assertion pins the forwarded value to {})

🤖 Generated with Claude Code

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>
@vercel

vercel Bot commented Aug 9, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Aug 9, 2026 1:32am

Request Review

@cursor

cursor Bot commented Aug 9, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Changes what reaches the function-execute boundary during condition evaluation; intended behavior-preserving because references are pre-resolved, but any leftover <block> tags in expressions would no longer be resolved server-side.

Overview
Condition evaluation no longer sends the run’s accumulated blockData to function_execute; it now passes blockData: {}, matching FunctionBlockHandler. blockNameMapping and blockOutputSchemas from collectBlockData are still included.

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 blockData on tool calls and add a regression case that mocks large collected outputs and asserts nothing is forwarded.

Reviewed by Cursor Bugbot for commit 93fd34f. Configure here.

@greptile-apps

greptile-apps Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR stops condition evaluations from forwarding accumulated block outputs after condition references have already been resolved, preventing request bodies from growing with run state.

  • Sends an empty blockData object through the function-execution boundary while retaining workflow metadata used by the request.
  • Adds regression coverage asserting collected outputs are not forwarded.

Confidence Score: 5/5

The 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.

Important Files Changed

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
Loading

Reviews (1): Last reviewed commit: "fix(condition): stop shipping all block ..." | Re-trigger Greptile

@icecrasher321
icecrasher321 merged commit 19c3171 into staging Aug 9, 2026
30 checks passed
@waleedlatif1
waleedlatif1 deleted the fix/condition-payload-size branch August 9, 2026 09:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant