Skip to content

fix(core): broadcast channel output for runs without inbound request context#18553

Closed
CalebBarnes wants to merge 1 commit into
caleb/processor-context-identityfrom
caleb/processor-context-channels
Closed

fix(core): broadcast channel output for runs without inbound request context#18553
CalebBarnes wants to merge 1 commit into
caleb/processor-context-identityfrom
caleb/processor-context-channels

Conversation

@CalebBarnes

Copy link
Copy Markdown
Member

What this ships

Agent runs on a channel-backed thread (Slack, Discord, etc.) now post their output back to the channel even when the run did not start from an inbound platform message. Heartbeat-triggered runs, Studio runs, and custom-UI / user-code runs against a channel thread previously produced no channel output at all — they were silently dropped.

Why

ChatChannelOutputProcessor only rendered to the channel when AgentChannels stashed a render context on requestContext during an inbound webhook (processChatMessage, approve/decline). Any run that did not originate from a platform message had no such context, so the processor early-returned and nothing reached the channel. Channel delivery was effectively coupled to the inbound request path.

How

  • ChatChannelOutputProcessor is now bound to its owning AgentChannels at construction.
  • When no inbound render context is present, the processor reconstructs it from the run's threadId (provided by the processor-identity work) via a new AgentChannels.buildRenderContextForThread(threadId). That method reads the thread's persisted channel coordinates (channel_platform, channel_externalThreadId), materializes the live channel thread handle, and delegates to the same _buildRenderContext the inbound paths use — so both paths produce an identical render context (single source of truth).
  • The inbound requestContext path is unchanged and still takes precedence (fast path).
  • Non-channel threads pass through untouched. The resolved render context (or null) is cached on state so it is built once per run, not per chunk.

Nothing new is persisted: every non-serializable field (adapter, closures, logger) is rebuilt live from the bound AgentChannels.

Stacking

Stacked on #18552 (processor context identity), which threads threadId to the output processor. This PR targets that branch and stays a draft until #18552 merges.

Test plan

  • pnpm build:core
  • pnpm --filter ./packages/core check (typecheck clean)
  • pnpm --filter ./packages/core test -- src/channels/ — all 238 channels tests pass, including 5 new fallback tests in output-processor.test.ts:
    • broadcasts on a channel-backed thread with no requestContext key
    • passes through when the thread has no channel metadata
    • passes through when the thread does not exist in storage
    • passes through when no threadId is provided
    • prefers the inbound requestContext render context over the fallback (no regression)

…context

ChatChannelOutputProcessor previously only rendered to the channel when
AgentChannels stashed a render context on requestContext during an inbound
platform webhook. Heartbeat, Studio, and custom-UI/user-code runs on a
channel-backed thread therefore never posted back.

Bind the owning AgentChannels into the processor and add a fallback: when no
inbound render context exists, reconstruct it from the run's threadId via
buildRenderContextForThread, which reads the thread's persisted channel
coordinates and delegates to the same _buildRenderContext used by the inbound
paths. The resolved context (or null for non-channel threads) is cached on
state so it is built once per run.

Co-Authored-By: Mastra Code (anthropic/claude-opus-4-8) <noreply@mastra.ai>
@changeset-bot

changeset-bot Bot commented Jun 27, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 7e6ca73

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

This PR includes changesets to release 22 packages
Name Type
@mastra/core Patch
mastracode 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
@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

@coderabbitai

coderabbitai Bot commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6bc00743-0f2f-4b90-9975-22590ae05e8e

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch caleb/processor-context-channels

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

@vercel

vercel Bot commented Jun 27, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
mastra-docs-1.x Ready Ready Preview, Comment Jun 27, 2026 9:59am
mastra-playground-ui Ready Ready Preview, Comment Jun 27, 2026 9:59am
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
mastra-studio-preview Ignored Ignored Jun 27, 2026 9:59am

Request Review

@CalebBarnes

Copy link
Copy Markdown
Member Author

Superseded by #18630, which collapses this two-PR stack into a single self-contained PR. Instead of threading new identity fields through the whole processor surface (old #18552), #18630 reads the run's threadId from the framework-populated MastraMemory request-context key via the validated parseMemoryRequestContext accessor — no processor API changes needed. The channels fix (this PR's content) is unchanged.

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