fix(core): broadcast to channels on any run on a channel-backed thread#18630
fix(core): broadcast to channels on any run on a channel-backed thread#18630CalebBarnes wants to merge 5 commits into
Conversation
…context Channel broadcast (Slack/Discord/etc.) only worked for agent runs that started from an inbound platform webhook, because ChatChannelOutputProcessor read its render context from a key that AgentChannels sets only on those inbound paths. Heartbeat, Studio, and custom-UI/user-code runs on a channel-backed thread never posted back to the channel. The output processor now reconstructs the render context from the thread itself when no inbound context is present: it reads the run's threadId from the framework-populated MastraMemory request-context key, then asks the bound AgentChannels to rebuild the render context from the thread's persisted channel coordinates (channel_platform, channel_externalThreadId) via the same _buildRenderContext used by the inbound path. Non-channel threads pass through untouched and the inbound fast path is unchanged. Co-Authored-By: Mastra Code (anthropic/claude-opus-4-8) <noreply@mastra.ai>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
1 Skipped Deployment
|
🦋 Changeset detectedLatest commit: 3bf0157 The changes in this PR will be included in the next version bump. This PR includes changesets to release 22 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 |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
WalkthroughAdds fallback render-context resolution in ChangesChannel Broadcast Fallback
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
PR triageLinked issue check skipped for core contributor @CalebBarnes. PR complexity score
Applied label: Changed test gateChanged tests failed against the base branch as expected. Label: |
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 `@packages/core/src/channels/agent-channels.ts`:
- Around line 1208-1210: The thread metadata read in the channel lookup path is
being cast directly to string, which bypasses strict validation and can pass
non-string persisted values into chat.thread(externalThreadId). Update the
metadata handling in the code that reads thread.metadata?.channel_platform and
thread.metadata?.channel_externalThreadId to validate both values are actually
strings before use, and return null when either is missing or invalid instead of
relying on casts.
🪄 Autofix (Beta)
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: CHILL
Plan: Pro
Run ID: 6aa6935a-3314-4260-9bcb-8b4870b150df
📒 Files selected for processing (4)
.changeset/wide-signs-send.mdpackages/core/src/channels/__tests__/output-processor.test.tspackages/core/src/channels/agent-channels.tspackages/core/src/channels/output-processor.ts
… render thread.metadata is persisted unknown; the previous `as string` casts let truthy non-strings reach chat.thread(externalThreadId). Narrow both values with a runtime typeof guard and pass through (return null) on invalid metadata. Addresses CodeRabbit review on #18630. Co-Authored-By: Mastra Code (anthropic/claude-opus-4-8) <noreply@mastra.ai>
What & why
Channel broadcast (Slack/Discord/etc.) only worked for agent runs that started from an inbound platform webhook.
ChatChannelOutputProcessorread its render context from arequestContextkey thatAgentChannelssets only on those inbound paths (processChatMessage, approve/decline). Any other run on a channel-backed thread — a heartbeat fire, a Studio run, a custom UI, or user code calling the agent directly — never posted its output back to the channel, because that key was absent.This makes the output processor self-sufficient: when there's no inbound render context, it reconstructs one from the thread itself.
How
ChatChannelOutputProcessoris now bound to its owningAgentChannelsat construction.state:CHAT_CHANNEL_RENDER_CONTEXT_KEYonrequestContext.threadIdfrom the framework-populatedMastraMemoryrequest-context key (via the validatedparseMemoryRequestContextaccessor), then callagentChannels.buildRenderContextForThread(threadId).buildRenderContextForThreadloads the Mastra thread, reads its persisted channel coordinates (channel_platform,channel_externalThreadId), materializes the live Chat SDKThreadhandle viachat.thread(externalThreadId), and delegates to the same_buildRenderContextthe inbound path uses — so both paths produce an identical render context (single source of truth). Returnsnullfor non-channel threads, which pass through untouched.Nothing new is persisted; the fallback rebuilds all live handles from the bound
AgentChannelsinstance plus the thread's stored coordinates.Test plan
ChatChannelOutputProcessor fallback render contextsuite inoutput-processor.test.ts:requestContextkeythreadIdrequestContextrender context still wins (fast path unchanged)pnpm --filter ./packages/core test -- --run src/channels/— 238 pass (9 files)pnpm --filter ./packages/core check— cleanELI5
When a run is started from a channel thread without an inbound “webhook” context, the system previously didn’t know where to send the model’s output. Now it can look up the thread and rebuild the channel routing info, so channel output broadcasting keeps working.
Summary
ChatChannelOutputProcessorto support a fallback render-context path whenrequestContextdoes not containCHAT_CHANNEL_RENDER_CONTEXT_KEY.CHAT_CHANNEL_RENDER_CONTEXT_KEYis present, it uses that render context and does not run the fallback.threadIdfrom the framework-stashedMastraMemoryrequest context.memorystore, validating persisted channel metadata (channel_platform,channel_externalThreadId) with runtimetypeofchecks, and ensuring the platform adapter/chat SDK prerequisites exist.chat.thread(externalThreadId)and building theChatChannelRenderContextviaAgentChannels.buildRenderContextForThread(...).AgentChannels.getOutputProcessors/ChatChannelOutputProcessorconstruction so the processor can callagentChannels.buildRenderContextForThread(threadId).@mastra/coredescribing the channel broadcasting fix.