Repository navigation
fix(core): keep generateTitle alive via serverless.waitUntil - #20996
abhiaiyer91 merged 5 commits into
Conversation
Detached void genTitle() never ran after HTTP freeze on Lambda/Vercel. Await title generation and persistence inside #executeOnFinish. Fixes mastra-ai#20682
🦋 Changeset detectedLatest commit: 88e5b58 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 |
|
@omkar1work is attempting to deploy a commit to the Mastra Team on Vercel. A member of the Team first needs to authorize it. |
PR triageThis PR needs to fix an existing issue. Please link an issue in the PR description, for example with Applied label: PRs without a linked issue will automatically close after 0 day(s) with the label. Changed test gateChanged Test Gate is pending. The |
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (7)
WalkthroughThe agent now accepts an optional ChangesThread title persistence
Estimated code review effort: 3 (Moderate) | ~25 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 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 |
Regression for mastra-ai#20682: createThread is intentionally slow so a detached void genTitle would lose the race; generate must not resolve until the title is persisted.
|
Added a focused regression test: @coderabbitai review |
|
|
|
I think its whack to slow down the whole process just for serverless |
Do not block generate()/stream() on title persistence. Accept optional platform waitUntil so serverless isolates keep the fire-and-forget work alive after the response (mastra-ai#20682).
|
@abhiaiyer91 fair — awaiting title gen on the hot path was the wrong tradeoff. Pushed a different approach:
import { waitUntil } from '@vercel/functions';
await agent.generate(msg, {
waitUntil,
memory: { thread: '...', resource: '...', options: { generateTitle: true } },
});Regression test now asserts generate resolves before title persistence, and that |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.changeset/await-generate-title-serverless-20682.md:
- Line 5: Add a short TypeScript example to the changeset showing generate() or
stream() called with the new optional waitUntil execution option, using a
platformWaitUntil value and preserving the existing feature description.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 08a88c56-bf74-409f-a653-fbe9c9ef428e
📒 Files selected for processing (6)
.changeset/await-generate-title-serverless-20682.mdpackages/core/src/agent/__tests__/title-generation.test.tspackages/core/src/agent/agent.tspackages/core/src/agent/agent.types.tspackages/core/src/agent/types.tspackages/core/src/agent/workflows/prepare-stream/map-results-step.ts
|
lets put waitUntil under a serverless key |
There was a problem hiding this comment.
Verdict: request changes
Findings
1. (blocking) The new option breaks the durable-parity typecheck gate.
waitUntil was added to AgentExecutionOptionsBase (packages/core/src/agent/agent.types.ts) but not to DurableHandledKeys, so packages/core/src/agent/durable/durable-parity.test-d.ts fails to compile:
src/agent/durable/durable-parity.test-d.ts(141,11): error TS2322:
Type 'boolean' is not assignable to type '{ ERROR: "Unhandled keys found"; keys: "waitUntil"; }'.
That gate exists precisely to catch a new base execution option that the durable path doesn't account for. It must be resolved deliberately — either handle waitUntil in the durable path or register it as intentionally-not-applicable there (the durable finish path already awaits title generation, so "not applicable" is likely the right answer, but it has to be declared).
2. (blocking) Maintainer direction is unaddressed. abhiaiyer91 asked on 2026-08-08T15:13Z — after the last push (abe094eb, 14:40Z) — to put waitUntil under a serverless key. The current diff still exposes it as a top-level execution option.
3. (blocking) The fix doesn't reach the runtime path the linked issue describes. #20682 reports titles dropped when Mastra is deployed on Lambda/Vercel. This PR only helps a caller who invokes agent.generate()/stream() directly and threads a platform waitUntil in by hand. Requests served through the Mastra server routes still drop the title, because nothing resolves waitUntil for them. Core already has the machinery for this: packages/core/src/channels/wait-until.ts exports resolveWaitUntil(c) (Cloudflare executionCtx, Netlify env.context) and ChannelConfig exposes waitUntil / resolveWaitUntil with the documented resolution order config.waitUntil → config.resolveWaitUntil(c) → resolveWaitUntil(c) (channels/agent-channels.ts:761). A per-call option that ignores that existing resolution chain is a second, parallel mechanism for the same concern. Reuse it (and reuse WaitUntilFn in agent/types.ts instead of re-declaring the signature inline).
4. (non-blocking) Changeset lacks a usage example. The repo's changeset path instructions ask for a short code example on new features; CodeRabbit flagged this and it is still open.
Correctness of what is there: the #executeOnFinish change itself is sound — the promise stays fire-and-forget by default, the .catch() logging is preserved so waitUntil never receives a rejecting promise, and typeof waitUntil === 'function' guards a non-callable value. The void titlePromise else-branch is exactly the pre-PR behavior. No behavior regression for callers that don't pass the option.
Test assessment: the v2 branch of the new test is meaningful — it asserts generate() resolves before persistence, that exactly one promise reaches waitUntil, that draining it persists the title, and that the persisted thread title matches. The v1 branch exercises generateLegacy (which already awaits inline) and never touches waitUntil, so it adds parity coverage rather than coverage of this change; that is acceptable. No test covers the durable path, consistent with finding 1 being unresolved.
Scope: focused, 6 files, no unrelated changes. Changeset present and correctly scoped to @mastra/core patch.
Verification
Pre-execution inspection: the diff touches only packages/core/src sources, one test file, and a changeset — no package.json scripts, lockfiles, test config, or CI workflows — so running it was safe. All commands run with env -u GH_TOKEN -u GITHUB_TOKEN.
| Command | Outcome |
|---|---|
git fetch origin pull/20996/head + checkout |
ok, head abe094eb |
pnpm turbo build --filter @mastra/core |
pass (14 tasks) |
pnpm --filter @mastra/core exec vitest run src/agent/__tests__/title-generation.test.ts |
pass — 56/56, including the two new waitUntil cases |
npx tsc --noEmit -p packages/core/tsconfig.json |
fail — 1 error, durable-parity.test-d.ts(141,11) (finding 1) |
CI on abe094eb: 5 check-runs green (Socket, Superagent, contributor trust, labeler). The build/test/typecheck workflows have not run on this fork commit, which is why the typecheck failure above is not visible on the PR. Two Vercel preview statuses are red; those are the unauthorized fork-deploy statuses from vercel[bot], not code failures. mergeStateStatus is blocked (no maintainer approval yet); the branch itself is mergeable with no conflicts.
Note: gh could not reach the GitHub API from this sandbox (TLS certificate verification failure); PR metadata, reviews, comments, and checks were read via the REST API over curl instead. Content collected is complete.
Existing review disposition
coderabbitai[bot](COMMENTED, 2026-08-08T14:49Z, 1 actionable comment) — changeset needs a usage example: confirmed. The repo's own path instructions require it for new features and the changeset still has no example. Recorded as finding 4.coderabbitai[bot]walkthrough summary — informational, no findings to disposition. Verified accurate against the diff.abhiaiyer91(2026-08-08T03:06Z) "I think its whack to slow down the whole process just for serverless" — addressed. Commitabe094ebreplaced theawaitwith the fire-and-forget +waitUntildesign; verified in the diff and by the new test assertingtitlePersisted === falseimmediately aftergenerate()resolves.abhiaiyer91(2026-08-08T15:13Z) "lets put waitUntil under a serverless key" — confirmed unaddressed. No commit after 14:40Z. Recorded as finding 2.dane-ai-mastra[bot]triage,changeset-bot,vercel[bot]— automation status, no substantive findings.
No review bot is pending on the head commit: CodeRabbit's latest review (14:49Z) post-dates the head commit (14:40Z), and gh check-runs show nothing queued or in progress.
Requested changes
- Resolve the
durable-parity.test-d.tstype error — addwaitUntiltoDurableHandledKeys(or the appropriate exclusion) and say in the code why the durable path doesn't need it. Confirm withpnpm --filter @mastra/core exec tsc --noEmit. - Move the option under a
serverlesskey as requested by the maintainer, e.g.serverless: { waitUntil }on the execution options. - Reconcile with the existing
waitUntilmachinery rather than adding a parallel one: type the new option asWaitUntilFnfromchannels/wait-untilinagent/types.ts(drop the re-declared inline signature), and wire server/HTTP-served runs throughresolveWaitUntil(c)so the deployment shape described in #20682 is actually fixed — or state explicitly in the PR body that call-site-only is the intended scope for this pass. - Add the short usage example to
.changeset/await-generate-title-serverless-20682.mdper the repo's changeset instructions.
Assumptions
- The v1/
generateLegacybranch of the new test not exercisingwaitUntilis deliberate and correct — the legacy finish path already awaits title generation, as the linked issue itself notes. - The two red Vercel statuses are fork-deploy authorization, not a code signal; they are excluded from the verdict.
- No changes were needed to
generateLegacy,streamLegacy,loop/network, ordurable/preparation.tsbecause all four already await title generation in their finish step.
Open questions
- Should
waitUntilbe resolved automatically at the server/channel layer (matchingChannelConfig's resolution chain) so deployed Mastra apps get the fix without touching call sites, or is an explicit per-call opt-in the intended contract? This is a product/API decision and it determines whether requested change 3 is a wiring task or a documentation task.
Address maintainer feedback and durable-parity gate: expose serverless.waitUntil, declare it intentionally unused on the durable path (finish already awaits titles), and document the call-site API.
|
@abhiaiyer91 moved — it’s now import { waitUntil } from '@vercel/functions';
await agent.generate(msg, {
serverless: { waitUntil },
memory: { thread: '...', resource: '...', options: { generateTitle: true } },
});Also fixed the durable-parity type gate ( Scope this pass: call-site opt-in. Auto-wiring through server/channel |
|
Closing this PR because it has had the needs-issue label for at least 0 days without a linked issue. Please open or link an issue first, then reopen this PR when it is ready. |
Summary
generate()/stream()stay fast.serverless.waitUntilso serverless platforms can keep that promise alive after the HTTP response ([BUG] Thread title is silently lost when generateTitle is used on serverless runtimes that freeze after the response (e.g. AWS Lambda) #20682).serverlessis declared in the durable-parity gate as intentionally unused there.Out of scope (follow-up): auto-resolving
waitUntilfor Mastra server/HTTP routes viaresolveWaitUntil(c)/ channel config. This PR is call-site opt-in.Test plan
title-generationregression: generate resolves before title persistence;serverless.waitUntilreceives the pending promiseserverlessserverlessnestingELI5
The agent saves the conversation title in the background so the user does not wait. Serverless platforms can use
waitUntilto keep this work running after the response ends.Changes
serverless.waitUntilsupport togenerate()andstream().waitUntilthrough memory persistence.@mastra/core.