Skip to content

improvement(condition): batch condition evaluation into one sandbox call, and stop the transport from undercutting a route's own deadline - #6854

Merged
icecrasher321 merged 3 commits into
stagingfrom
fix/condition-batch-eval
Aug 19, 2026
Merged

improvement(condition): batch condition evaluation into one sandbox call, and stop the transport from undercutting a route's own deadline#6854
icecrasher321 merged 3 commits into
stagingfrom
fix/condition-batch-eval

Conversation

@icecrasher321

Copy link
Copy Markdown
Collaborator

What

Two changes to the same failure, from opposite ends.

apps/sim/executor/handlers/condition/condition-handler.ts — a condition block spent one function_execute round trip per branch, so a four-branch block that fell through to else paid four sandbox executions before it could route. It now builds one script that tests each expression in order and returns the index of the first truthy one.

apps/sim/tools/index.ts — a timeout param bounds the work an internal route was asked to do (the code a sandbox runs, the upstream call a proxy makes). The fetch around it also pays authentication, body parsing, workspace authorization, worker acquisition, and response serialization, none of which that budget was sized for. Arming the client with the bare number made the caller give up at the same instant the route's own deadline fired, so the route could never win its own race — you saw an unattributable Request timed out instead of Function execution timed out after 5000ms. Adds 30s of transport headroom, sized above the isolated-vm worker's own 10s startup budget so a cold worker spawn stays inside the deadline rather than aborting it.

Behavior that is deliberately unchanged

  • Ordering and short-circuiting. An expression is only reached once every earlier one returned falsy, so a later expression that throws is still never reached and the run takes the same branch it took before.
  • Error attribution. The script's catch reports the index it was on as data rather than rethrowing, so the handler still throws Evaluation error in condition "<title>" naming the branch that actually failed.
  • else handling. An else wins as soon as it is reached, so only the branches ahead of it are testable — matching the original loop, which returned on the first else it walked past rather than assuming else comes last. The else expression never reaches the sandbox.
  • Execution deadlines. When a caller/execution abort signal is present it still bounds the call; the headroom only extends the local hang net.

The fallback, and why it is load-bearing

A batch that produces no verdict falls back to one call per branch — exactly what this handler did before. That is not a competing second system kept for comfort: a syntax error anywhere in the list fails the whole script at parse time, whereas evaluating one at a time only reaches, and so only fails on, the branches the run actually takes. Without it, a workflow with a broken later branch that always matches an earlier one would newly start failing. There is no way to isolate a parse failure per-branch inside a single script, so the second pass is the only recovery.

Two failures skip the fallback, because retrying would make them worse rather than recover them:

  • A timed-out batch — re-running every branch against the same stalled transport turns one slow call into as many slow calls as there are branches.
  • A cancelled run — every retry aborts on arrival.

Both surface the batch failure as it stands. The whole list was one call, so no single branch owns that failure; the error names the first, where evaluation started.

An unrecognized reply is treated as no verdict rather than as "nothing matched", so a garbled response cannot silently route the run down the else path.

Tests

apps/sim/executor/handlers/condition/condition-handler.test.ts — the existing suite was rewritten onto the batched verdict shape (one call, one verdict) and pins the new behavior: a whole list in a single call, declaration order preserved in the emitted script, the else branch never sent, per-branch fallback on a no-verdict batch, no fan-out on a timeout or a cancelled run, an out-of-range index falling back, a garbled reply not taking the else path, and no secret leaking into logs on either failure path.

apps/sim/tools/index.test.ts — asserts the transport is still waiting at the instant the route's own 5s budget expires, and gives up at 35s.

Verification

  • bun run test on both files: 204 passed
  • bun run type-check (apps/sim): clean
  • biome check on all four files: clean
  • bun run check:api-validation: passes

Known tradeoffs

  • A hard sandbox timeout loses the per-branch index (the script never returns), so a stalled batch names the first branch rather than the branch that stalled. Documented at the throw site.
  • The whole list now shares one 5s CONDITION_TIMEOUT_MS instead of 5s per branch. Conditions are inlined boolean expressions with no I/O, so this is not a budget any real list approaches.
  • isTimeoutFailure classifies by message text, the same way isRetryableFailure does in @/tools. A sandbox syntax error whose message happened to contain "timeout" would skip the fallback and fail a run that would otherwise have matched an earlier branch. It needs the user's own condition source to contain that substring and a syntax error in the list.

🤖 Generated with Claude Code

icecrasher321 and others added 2 commits August 19, 2026 12:21
…ion budget

A `timeout` param bounds the work an internal route was asked to do — the code
a sandbox runs, the upstream call a proxy route makes. The fetch around it also
pays authentication, body parsing, workspace authorization, worker acquisition,
and response serialization, none of which that budget was sized for. Arming the
client with the bare number made the caller give up at the same instant the
route's own deadline fired, so the route could never win the race and report
which part actually ran long — the caller saw an unattributable
`Request timed out` instead of `Function execution timed out after 5000ms`.

Add 30s of headroom, sized above the isolated-vm worker's own 10s startup
budget so a cold worker spawn stays inside the transport deadline rather than
aborting it. An execution abort signal, when present, still bounds the call.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A condition block spent one `function_execute` round trip per branch, so a
four-branch block that fell through to `else` paid four sandbox executions
before routing. Build one script that tests each expression in order and
returns the index of the first truthy one.

Ordering and short-circuiting are unchanged: an expression is only reached once
every earlier one returned falsy, so a later expression that throws is still
never reached and the run takes the same branch it took before. The script's
`catch` reports the index it was on as data rather than rethrowing, which is
what lets the handler still name the failing branch in its error.

A batch that produces no verdict falls back to one call per branch — the path
this handler used before. That is load-bearing rather than redundant: a syntax
error anywhere in the list fails the whole script at parse time, while
evaluating one at a time only reaches, and so only fails on, the branches the
run actually takes. A timed-out or cancelled batch skips the fallback, which
would otherwise re-run every branch against the same stall.

An unrecognized reply is treated as no verdict rather than as "nothing
matched", so a garbled response cannot silently route the run down the else
path.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 19, 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 19, 2026 7:38pm

Request Review

@cursor

cursor Bot commented Aug 19, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Changes workflow routing and sandbox invocation patterns for condition blocks; incorrect batch/fallback logic could mis-route runs or change error behavior. Transport timeout extension affects all function_execute internal calls but is narrowly scoped to deadline math.

Overview
Condition blocks now evaluate all testable branches in one function_execute call instead of one sandbox round trip per branch. A generated script tests expressions in order (same short-circuiting as before), returns the first matching index or structured throw metadata, and never sends the else branch to the sandbox. When the batch yields no usable verdict (parse errors, garbled replies, out-of-range indices), the handler falls back to per-branch calls; timeouts and aborted runs skip that fallback so one slow or cancelled batch does not fan out into N retries.

Expression wrapping uses a shared Boolean(\n…\n) helper so batch and fallback paths agree on expressions with trailing comments.

For internal tool routes, when a timeout param is set, the client transport deadline is now timeout + 30s (INTERNAL_ROUTE_TRANSPORT_OVERHEAD_MS), so the route can finish auth, worker startup, and report its own execution timeout instead of the fetch aborting at the same instant as the sandbox budget.

Reviewed by Cursor Bugbot for commit 3c3ad93. Configure here.

@greptile-apps

greptile-apps Bot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR batches ordered condition evaluation into one sandbox request while retaining per-branch fallback for responses without a usable verdict, and adds transport headroom beyond internal routes’ own execution budgets.

  • Builds a single short-circuiting condition script with indexed match and error verdicts.
  • Retains individual evaluation for recoverable batch failures while avoiding retries after timeout or cancellation.
  • Adds 30 seconds of transport headroom to explicit internal-route timeouts.
  • Updates condition-handler and transport-timeout coverage for the new behavior.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains; the previously reported evaluation-context typing issue is fixed at HEAD.

Important Files Changed

Filename Overview
apps/sim/executor/handlers/condition/condition-handler.ts Batches condition expressions into one ordered sandbox script, validates structured verdicts, and preserves individual fallback for recoverable failures.
apps/sim/executor/handlers/condition/condition-handler.test.ts Updates existing mocks for batched verdicts and covers ordering, fallback, malformed responses, timeout, cancellation, comments, and error redaction.
apps/sim/tools/index.ts Adds 30 seconds of headroom between an explicit internal-route work timeout and the surrounding fetch deadline.
apps/sim/tools/index.test.ts Verifies that a 5-second route budget leaves the transport active until its new 35-second deadline.

Sequence Diagram

sequenceDiagram
  participant Handler as Condition Handler
  participant Tool as executeTool
  participant Route as Internal Function Route
  participant Sandbox as Sandbox
  Handler->>Tool: Batched condition script (5s work budget)
  Tool->>Route: Fetch (35s transport deadline)
  Route->>Sandbox: Execute ordered expressions
  alt Usable verdict
    Sandbox-->>Route: matchedIndex / no-match / threwAtIndex
    Route-->>Handler: Structured result
  else Recoverable no-verdict
    Route-->>Handler: Failure or unrecognized result
    Handler->>Tool: Evaluate branches individually
  else Timeout or cancellation
    Route-->>Handler: Timeout/cancellation failure
    Handler-->>Handler: Surface failure without fallback fan-out
  end
Loading

Reviews (2): Last reviewed commit: "fix(executor): wrap condition expression..." | Re-trigger Greptile

@icecrasher321 icecrasher321 changed the title Batch condition evaluation into one sandbox call, and stop the transport from undercutting a route's own deadline improvement(condition): batch condition evaluation into one sandbox call, and stop the transport from undercutting a route's own deadline Aug 19, 2026
Comment thread apps/sim/executor/handlers/condition/condition-handler.ts
Comment thread apps/sim/executor/handlers/condition/condition-handler.ts Outdated
The batched script put each expression on its own line inside `Boolean(...)`
so a trailing line comment ended before the closing parenthesis; the per-branch
fallback still inlined `Boolean(${expression})` on one line. That made the
recovery path stricter than the path it recovers — a batch that failed to parse
because of a later branch would fall back and then reject an earlier
comment-bearing branch it should have matched.

Both paths now wrap through `buildBooleanTest`, so they cannot drift again.
Also narrows the evaluation-context boundary from `Record<string, any>` to
`Record<string, unknown>`; the context is only ever serialized, never indexed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@icecrasher321

Copy link
Copy Markdown
Collaborator Author

bugbot run

@icecrasher321

Copy link
Copy Markdown
Collaborator Author

@greptile

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 3c3ad93. Configure here.

@icecrasher321
icecrasher321 merged commit 9864f5c into staging Aug 19, 2026
30 checks passed
@icecrasher321
icecrasher321 deleted the fix/condition-batch-eval branch August 19, 2026 19:45
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