Skip to content

fix(agents-runtime): avoid stack overflow on large live batches#4698

Open
KyleAMathews wants to merge 3 commits into
mainfrom
fix-streamdb-stack-overflow-pr
Open

fix(agents-runtime): avoid stack overflow on large live batches#4698
KyleAMathews wants to merge 3 commits into
mainfrom
fix-streamdb-stack-overflow-pr

Conversation

@KyleAMathews

@KyleAMathews KyleAMathews commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Fixes large JSON StreamDB batches in the agents runtime/server by avoiding spread-based array appends and avoiding StreamResponse.json() for server-side JSON reads. This prevents RangeError: Maximum call stack size exceeded during wake processing and manifest rehydration, with no intended behavior change.

Root Cause

There were two variants of the same failure mode:

  1. processWake accumulated live StreamDB change events with calls like:
catchUpEvents.push(...changeEvents)
deltaEvents.push(...changeEvents)

For sufficiently large live batches, spreading changeEvents passes every event as a separate function argument to push. V8 has an argument/stack limit, so a large batch can throw before the runtime finishes processing the wake.

  1. StreamClient.readJson delegated to @durable-streams/client's StreamResponse.json(). That dependency currently flattens parsed JSON arrays internally with the same spread pattern, so large streams can also fail during server rehydration paths such as future-send/cron manifest loading:
RangeError: Maximum call stack size exceeded
    at StreamResponseImpl.json (.../@durable-streams/client/dist/index.js:1904:11)
    at async StreamClient.readJson (.../packages/agents-server/src/stream-client.ts:407:12)

Approach

For runtime live-batch accumulation, add a small appendAll helper that appends items with a loop instead of argument spreading:

function appendAll<T>(target: Array<T>, items: Array<T>): void {
  for (const item of items) {
    target.push(item)
  }
}

For server JSON reads, have StreamClient.readJson consume response.jsonStream() and collect items one-by-one instead of calling response.json():

const items: Array<T> = []
for await (const item of response.jsonStream()) {
  items.push(item)
}
return items

This keeps the fix local while avoiding the dependency's large-array spread path.

Key Invariants

  • Batch ordering is preserved exactly: items are appended/read in source order.
  • Wake offset / ack offset behavior is unchanged.
  • Stream read options are unchanged: non-live reads still start from the same offset.
  • Large batches no longer depend on JavaScript function-argument limits.

Non-goals

  • This does not change StreamDB batching semantics.
  • This does not add chunking, backpressure, or batching thresholds.
  • This does not alter wake selection, freshness detection, ack behavior, or manifest schema.
  • This does not patch @durable-streams/client; it avoids the problematic API at the agents-server call site.

Trade-offs

The loop/streaming code is slightly more verbose than push(...items) or response.json(), but it is safe for arbitrary batch sizes and avoids known large-array spread failure modes. A dependency-level fix would be broader, but this PR keeps the immediate agents runtime/server fix small and easy to review.

Verification

pnpm install
pnpm --filter @electric-ax/agents-runtime exec vitest run test/process-wake.test.ts --reporter=dot
pnpm --filter @electric-ax/agents-server exec vitest run test/stream-client.test.ts --reporter=dot
GITHUB_BASE_REF=main node scripts/check-changeset.mjs

Results:

packages/agents-runtime/test/process-wake.test.ts: passed
packages/agents-server/test/stream-client.test.ts: 10 passed
Changesets cover affected packages

Also ran:

pnpm --filter @electric-ax/agents-server typecheck

That still fails on existing local workspace resolution/type issues around @electric-sql/client and related implicit-any errors, but the new stream-client.ts type error found during iteration was fixed.

Files changed

  • packages/agents-runtime/src/process-wake.ts — replaces spread-based live-batch appends with appendAll.
  • packages/agents-server/src/stream-client.ts — changes readJson to consume jsonStream() one item at a time instead of response.json().
  • packages/agents-server/test/stream-client.test.ts — adds a regression test ensuring large reads do not call StreamResponse.json().
  • .changeset/large-live-batches.md — adds patch changesets for @electric-ax/agents-runtime and @electric-ax/agents-server.

@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Electric Agents Desktop Builds

Build artifacts for commit c7292d9.

Platform Status Artifact
macOS Apple Silicon Passed DMG
macOS Intel Passed DMG
Windows x64 Passed Installer
Linux x64 Passed AppImage / deb

Workflow run

@codecov

codecov Bot commented Jul 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 58.60%. Comparing base (3ef7614) to head (c7292d9).
⚠️ Report is 1 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4698      +/-   ##
==========================================
- Coverage   60.20%   58.60%   -1.60%     
==========================================
  Files         412      352      -60     
  Lines       44396    41030    -3366     
  Branches    12586    11970     -616     
==========================================
- Hits        26727    24046    -2681     
+ Misses      17587    16944     -643     
+ Partials       82       40      -42     
Flag Coverage Δ
electric-telemetry ?
elixir ?
packages/agents 72.64% <ø> (ø)
packages/agents-mcp ?
packages/agents-mobile 80.67% <ø> (ø)
packages/agents-runtime 83.73% <100.00%> (+0.01%) ⬆️
packages/agents-server 75.64% <100.00%> (-0.01%) ⬇️
packages/agents-server-ui 8.32% <ø> (ø)
packages/electric-ax 51.06% <ø> (ø)
packages/experimental ?
packages/react-hooks ?
packages/start ?
packages/typescript-client ?
packages/y-electric ?
typescript 58.60% <100.00%> (-1.44%) ⬇️
unit-tests 58.60% <100.00%> (-1.60%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Electric Agents Mobile Build

Local mobile checks ran for commit c7292d9.

The EAS Android preview build was skipped because the mobile-eas-build label is not present.
Add the mobile-eas-build label to this PR to produce an installable preview build.

Workflow run

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