Skip to content

fix(core): keep generateTitle alive via serverless.waitUntil - #20996

Merged
abhiaiyer91 merged 5 commits into
mastra-ai:mainfrom
edenbuilds:fix/await-generate-title-serverless-20682
Aug 10, 2026
Merged

abhiaiyer91 merged 5 commits into
mastra-ai:mainfrom
edenbuilds:fix/await-generate-title-serverless-20682

Conversation

@edenbuilds

@edenbuilds edenbuilds commented Aug 8, 2026 •

Copy link
Copy Markdown
Contributor

Summary

import { waitUntil } from '@vercel/functions';

await agent.generate('Name this conversation', {
  serverless: { waitUntil },
  memory: {
    thread: 'thread-1',
    resource: 'user-1',
    options: { generateTitle: true },
  },
});

Out of scope (follow-up): auto-resolving waitUntil for Mastra server/HTTP routes via resolveWaitUntil(c) / channel config. This PR is call-site opt-in.

Test plan

  • title-generation regression: generate resolves before title persistence; serverless.waitUntil receives the pending promise
  • Durable parity type gate includes serverless
  • Maintainer check on serverless nesting

ELI5

The agent saves the conversation title in the background so the user does not wait. Serverless platforms can use waitUntil to keep this work running after the response ends.

Changes

  • Keep title generation and persistence fire-and-forget.
  • Add optional serverless.waitUntil support to generate() and stream().
  • Pass waitUntil through memory persistence.
  • Continue logging title-generation errors without failing the agent run.
  • Add regression coverage for delayed title persistence and pending promise registration.
  • Add a patch changeset for @mastra/core.

Detached void genTitle() never ran after HTTP freeze on Lambda/Vercel.
Await title generation and persistence inside #executeOnFinish.

Fixes mastra-ai#20682
@changeset-bot

changeset-bot Bot commented Aug 8, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 88e5b58

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 25 packages
Name Type
@mastra/core Patch
@mastra/code-sdk Patch
mastracode Patch
@mastra/factory Patch
@mastra/mcp-docs-server Patch
@internal/playground Patch
@mastra/client-js Patch
@mastra/opencode Patch
@mastra/longmemeval Patch
mastra Patch
@mastra/deployer-cloud Patch
@mastra/react Patch
@mastra/playground-ui Patch
@mastra/server Patch
@mastra/deployer Patch
create-mastra Patch
create-factory Patch
@mastra/express Patch
@mastra/fastify Patch
@mastra/hono Patch
@mastra/koa Patch
@mastra/nestjs Patch
@mastra/next Patch
@mastra/tanstack-start Patch
@mastra/temporal Patch

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

@vercel

vercel Bot commented Aug 8, 2026

Copy link
Copy Markdown

@omkar1work is attempting to deploy a commit to the Mastra Team on Vercel.

A member of the Team first needs to authorize it.

@dane-ai-mastra dane-ai-mastra Bot added the complexity: low Low-complexity PR label Aug 8, 2026
@dane-ai-mastra

dane-ai-mastra Bot commented Aug 8, 2026 •

Copy link
Copy Markdown
Contributor

PR triage

This PR needs to fix an existing issue. Please link an issue in the PR description, for example with Fixes #1234 or Closes #1234.

Applied label: needs-issue

PRs without a linked issue will automatically close after 0 day(s) with the label.


Changed test gate

Changed Test Gate is pending. The Changed Test Gate / changed-tests check will update the test label when it completes.

@coderabbitai

coderabbitai Bot commented Aug 8, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: fc4c296f-13fd-4b16-a6e3-c6c453f893ff

📥 Commits

Reviewing files that changed from the base of the PR and between abe094e and 88e5b58.

📒 Files selected for processing (7)
  • .changeset/await-generate-title-serverless-20682.md
  • packages/core/src/agent/__tests__/title-generation.test.ts
  • packages/core/src/agent/agent.ts
  • packages/core/src/agent/agent.types.ts
  • packages/core/src/agent/durable/durable-parity.test-d.ts
  • packages/core/src/agent/types.ts
  • packages/core/src/agent/workflows/prepare-stream/map-results-step.ts

Walkthrough

The agent now accepts an optional serverless.waitUntil callback for asynchronous thread-title persistence. The default path remains non-blocking. Tests cover delayed persistence for AI SDK v1 and v2. A patch changeset documents the serverless support.

Changes

Thread title persistence

Layer / File(s) Summary
Expose waitUntil execution options
packages/core/src/agent/agent.types.ts, packages/core/src/agent/types.ts, packages/core/src/agent/durable/durable-parity.test-d.ts
Agent execution and finish options accept an optional waitUntil callback. Durable parity handling recognizes the serverless option without forwarding it through durable execution.
Register asynchronous title persistence
packages/core/src/agent/agent.ts, packages/core/src/agent/workflows/prepare-stream/map-results-step.ts
Title generation and persistence use a retained promise. The promise is passed to waitUntil when configured and remains detached otherwise.
Validate delayed persistence
packages/core/src/agent/__tests__/title-generation.test.ts, .changeset/await-generate-title-serverless-20682.md
Regression tests verify blocking legacy behavior and deferred v2 persistence. The changeset documents the optional serverless support.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

Suggested reviewers: abhiaiyer91

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes address issue #20682 by registering detached title-generation work with optional waitUntil while keeping agent execution non-blocking.
Out of Scope Changes check ✅ Passed The changeset, implementation, public types, integration update, and regression tests all support the linked serverless title-persistence fix.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the serverless waitUntil change and uses an imperative action, despite exceeding the ideal 50-character length.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

edenbuilds and others added 2 commits August 8, 2026 08:20
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.
@edenbuilds

Copy link
Copy Markdown
Contributor Author

Added a focused regression test: createThread is delayed so a detached void genTitle() would lose the race, and generate/generateLegacy must not resolve until the title is persisted — no post-await setTimeout.

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 8, 2026 •

Copy link
Copy Markdown
Contributor

@edenbuilds I will review the title-persistence change and the new regression test.

⚠️ Action not completed

Already reviewed.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@abhiaiyer91

Copy link
Copy Markdown
Member

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).
@edenbuilds

Copy link
Copy Markdown
Contributor Author

@abhiaiyer91 fair — awaiting title gen on the hot path was the wrong tradeoff.

Pushed a different approach:

  • title generation stays fire-and-forget (generate/stream are not slowed)
  • optional waitUntil on execution options registers that promise with the platform (Vercel @vercel/functions, CF ExecutionContext.waitUntil, etc.) so serverless freezes don’t drop it
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 waitUntil receives the pending promise. Happy to adjust if you’d rather wire waitUntil automatically from the server/channel layer instead of the call site.

@edenbuilds edenbuilds changed the title fix(core): await generateTitle so serverless runtimes persist titles fix(core): keep generateTitle alive via waitUntil on serverless Aug 8, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

📥 Commits

Reviewing files that changed from the base of the PR and between 18da797 and abe094e.

📒 Files selected for processing (6)
  • .changeset/await-generate-title-serverless-20682.md
  • packages/core/src/agent/__tests__/title-generation.test.ts
  • packages/core/src/agent/agent.ts
  • packages/core/src/agent/agent.types.ts
  • packages/core/src/agent/types.ts
  • packages/core/src/agent/workflows/prepare-stream/map-results-step.ts

Comment thread .changeset/await-generate-title-serverless-20682.md Outdated
@abhiaiyer91

Copy link
Copy Markdown
Member

lets put waitUntil under a serverless key

@mastra-platform mastra-platform Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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. Commit abe094eb replaced the await with the fire-and-forget + waitUntil design; verified in the diff and by the new test asserting titlePersisted === false immediately after generate() 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

  1. Resolve the durable-parity.test-d.ts type error — add waitUntil to DurableHandledKeys (or the appropriate exclusion) and say in the code why the durable path doesn't need it. Confirm with pnpm --filter @mastra/core exec tsc --noEmit.
  2. Move the option under a serverless key as requested by the maintainer, e.g. serverless: { waitUntil } on the execution options.
  3. Reconcile with the existing waitUntil machinery rather than adding a parallel one: type the new option as WaitUntilFn from channels/wait-until in agent/types.ts (drop the re-declared inline signature), and wire server/HTTP-served runs through resolveWaitUntil(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.
  4. Add the short usage example to .changeset/await-generate-title-serverless-20682.md per the repo's changeset instructions.

Assumptions

  • The v1/generateLegacy branch of the new test not exercising waitUntil is 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, or durable/preparation.ts because all four already await title generation in their finish step.

Open questions

  • Should waitUntil be resolved automatically at the server/channel layer (matching ChannelConfig'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.
@edenbuilds

Copy link
Copy Markdown
Contributor Author

@abhiaiyer91 moved — it’s now serverless.waitUntil:

import { waitUntil } from '@vercel/functions';

await agent.generate(msg, {
  serverless: { waitUntil },
  memory: { thread: '...', resource: '...', options: { generateTitle: true } },
});

Also fixed the durable-parity type gate (serverless is intentionally unused there — durable finish already awaits titles) and added the changeset example.

Scope this pass: call-site opt-in. Auto-wiring through server/channel resolveWaitUntil(c) is a follow-up if you want deployed HTTP routes covered without touching callers.

@edenbuilds edenbuilds changed the title fix(core): keep generateTitle alive via waitUntil on serverless fix(core): keep generateTitle alive via serverless.waitUntil Aug 9, 2026
@dane-ai-mastra dane-ai-mastra Bot added the needs-issue PR is missing a linked issue label Aug 9, 2026
@dane-ai-mastra

dane-ai-mastra Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

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.

@dane-ai-mastra dane-ai-mastra Bot closed this Aug 9, 2026
@abhiaiyer91 abhiaiyer91 reopened this Aug 10, 2026
@abhiaiyer91
abhiaiyer91 merged commit a3a3624 into mastra-ai:main Aug 10, 2026
9 of 11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

complexity: low Low-complexity PR needs-issue PR is missing a linked issue

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants