fix(core): keep array textStream valid when the first chunk has elements - #21559
Conversation
createJsonTextStreamTransformer emitted a fully-closed array whenever the
first object chunk arrived with elements already in it, then kept appending
later elements plus a closing bracket. Coarse or batched provider deltas hit
this path and produced invalid JSON like [{"a":1}],{"a":2}] that JSON.parse
rejects.
Hold the first array chunk back and only decide at flush: if a second chunk
arrives, stream incrementally; if the stream ends after one chunk, emit the
complete array as a single closed JSON string. This preserves the existing
single-chunk textStream contract and also fixes a latent case where a lone
empty chunk produced a bare [.
Fixes mastra-ai#18758.
🦋 Changeset detectedLatest commit: b2cc3bf The changes in this PR will be included in the next version bump. This PR includes changesets to release 25 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
@uuzzrm is attempting to deploy a commit to the Mastra Team on Vercel. A member of the Team first needs to authorize it. |
PR triageLinked issue check passed (#18758). Mastra uses CodeRabbit for automated code reviews. Please address all feedback from CodeRabbit by either making changes to your PR or leaving a comment explaining why you disagree with the feedback. Since CodeRabbit is an AI, it may occasionally provide incorrect feedback. PR complexity score
Applied label: Changed test gateChanged Test Gate is pending. The |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
WalkthroughThe array ChangesArray textStream JSON handling
Estimated code review effort: 2 (Simple) | ~15 minutes Merge Risk: ⚪ Minimal · up to This localized fix preserves the existing single-chunk behavior while correcting multi-chunk array streaming, and no actionable merge-blocking risk remains beyond normal checks and review. Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Verdict: approve
Findings
- Correctness: No blocking findings.
createJsonTextStreamTransformernow buffers the first array snapshot, preserving a complete one-chunk JSON array while switching to incremental emission when a second snapshot arrives. This prevents a closed first array from being followed by appended elements and keeps both coarse and fine-grained streams parseable (packages/core/src/stream/base/output-format-handlers.ts:766-824). - Tests: The regression coverage meaningfully exercises a non-empty first snapshot, a complete single snapshot, a lone empty snapshot, and fine-grained growth (
packages/core/src/stream/base/output-format-handlers.test.ts:1255-1302). - Scope: The runtime change, focused tests, and patch changeset are coherent and limited to issue #18758.
- Pattern consistency: The implementation remains inside the existing output-format transformer and retains snapshot-length delta emission used by the prior implementation; no new abstraction or API contract is introduced.
Verification
env -u GH_TOKEN -u GITHUB_TOKEN pnpm install— passed; only expected missing pre-build CLI symlink warnings.env -u GH_TOKEN -u GITHUB_TOKEN pnpm build:core— passed (14/14 tasks).env -u GH_TOKEN -u GITHUB_TOKEN pnpm --filter ./packages/core exec vitest run src/stream/base/output-format-handlers.test.ts --reporter=dot --bail 1— passed (1 file, 40 tests, no type errors).env -u GH_TOKEN -u GITHUB_TOKEN pnpm --filter ./packages/core exec vitest run src/stream/base --reporter=dot --bail 1— passed (5 files, 100 tests, no type errors).env -u GH_TOKEN -u GITHUB_TOKEN pnpm --filter ./packages/core check— passed.gh pr checks 21559— CodeRabbit and security/label checks passed. Two Vercel deployment checks report authorization-required failures; these do not exercise the changed core package and are unrelated to this diff.
Existing review disposition
- CodeRabbit correctness/risk review — addressed: The bot found no actionable merge-blocking defect and rated the localized change minimal risk; direct inspection and focused execution agree.
- CodeRabbit ESLint installation warning — refuted as a PR finding: This was a bot-tool installation failure rather than a reported source violation. The package typecheck and the full focused stream/base suite pass locally.
- CodeRabbit “fix failing CI” finishing touch — refuted as a code finding: The only failing checks are Vercel authorization gates for docs/playground deployments, while this PR changes only core transformer code, its tests, and a changeset.
- dane-ai-mastra triage — addressed: Linked issue validation passed, complexity is low, tests are included, and the CodeRabbit signal has been dispositioned.
- changeset-bot release notice — addressed: A valid patch changeset for
@mastra/coreis present. - No human reviews or unresolved inline review threads were present.
Adversarial check
The strongest request-changes case is that delaying the first emitted array chunk changes streaming latency for single-snapshot consumers; it does not survive because preserving the existing one-chunk closed-array contract requires waiting for stream completion, while multi-snapshot consumers begin valid incremental output as soon as the second snapshot establishes that mode.
Assumptions
- Preserving the existing single-chunk output boundary is deliberate and preferable to always emitting separate opening/content/closing chunks, based on the existing contract and regression tests.
- Vercel authorization failures are repository deployment-environment limitations, not evidence of a defect in this core-only change.
Open questions
- None.
Description
createJsonTextStreamTransformerproduced invalid JSON for an arraytextStreamwhenever the firstobjectchunk already contained elements. Coarse or batched provider deltas deliver the array-so-far as the first chunk, and the transformer's first-chunk fast path emitted a fully-closed array ([{\"a\":1}]) that later chunks then appended to:This fix holds the first array chunk back and only decides at flush time:
That keeps the existing single-chunk
textStreamcontract (one complete array in one chunk) intact, fixes the multi-chunk case, and also makes a lone empty chunk emit[]instead of a bare[.Related issue(s)
Fixes #18758
Note: #18759 targets the same bug with an always-incremental approach that changes single-chunk chunk boundaries and currently fails an existing loop test. This PR keeps those boundaries untouched instead.
Type of change
Checklist
ELI5
This fix keeps streamed array data as one valid JSON array. It handles both multi-chunk streams and streams that contain only one chunk.
Changes
createJsonTextStreamTransformer.[]for a lone empty chunk.@mastra/core.