Libra CodeHub

CedarCopilot/cedar-mail

Branch: staging

feat(notify): flag linked Slack channels as customer-facing so internal notifications DM instead

merged#2470CedarCopilot

CedarCopilot wants to merge 0 commits into staging from fix/notify-user-block-customer-facing-channels

Live on prod, no production signal yetTimeline and evidence
  1. Opened
    Aug 13, 2026, 4:28 PM
  2. Merged
    Aug 13, 2026, 7:31 PM
  3. Live on prod
    Aug 13, 2026, 7:31 PM
  4. Observed 3 hours
    Aug 13, 2026, 7:31 PM
  5. Pipelines steady after this deploy
    Aug 13, 2026, 7:31 PM
  6. Unobserved

    Live on prod, no production signal yet

    Aug 13, 2026, 10:36 PM

Behaviors Libra is checking

Chat workflow behavior from context-formatting/conversation-context.ts keeps working in prod.Inconclusivelow confidence

Strict CloudWatch fallback found 4 prod failure log lines for mirror.upsertEmailThread failed (write CONNECT_TIMEOUT aws-1-us-east-2.pooler.supabase.com:6543), but this domain-wide failure family is not tied to this intent.

prod, checked Aug 13, 2026, 10:36 PM

Libra has verdicts on 0 of 1 tracked behaviors on prod; 1 is still being checked. Libra checks hourly for 3 days after each deploy.

What was wrong

Twice, internal automations posted into a customer's shared Slack channel instead of DMing the user:

AutomationRun IDLanded in
Hey Telo meeting-prep (7bef9d17)6590528d-4e94-4839-82a7-cfa6e173e5f2#cedar-heytelo
Concentrate crm-updater P0 bug alert (449bc742)1b3f3db6-… / 09dcbaf3-…#cedar-concentrate

Both bodies were clearly internal ("Get Jesse to investigate…", staging Cedar links) , the customer saw them. The channel ID is surfaced in the agent's conversation context (as #<id> on the deal's Slack events), so the model sometimes fills notify-user's slack.channelId with the customer channel.

Approach: label, don't hard-block

A hard reject was considered and rejected: a shared prospect channel is also deal-linked, and teams legitimately post customer-facing output there (e.g. a meeting recap the customer should see). Blocking all posts to deal-linked channels would break that.

Instead we make the intent unmistakable to the agent and let it choose:

  • The <linked_slack_channel> block in conversation context now states the channel is CUSTOMER-FACING (the prospect sees it), and that internal notifications (prep, coaching, deal/bug alerts, internal status) must go to the user's DM , omit slack.channelId. Only messages written for the customer belong there.
  • notify-user's channelId description says the same.

Customer-facing content can still be posted to the channel , this only steers internal notifications to the DM.

Files

  • mastra/utils/context-formatting/conversation-context.ts , customer-facing label on <linked_slack_channel>.
  • mastra/tools/notify/notifyUserTool.ts , channelId param description.

How to test

  • pnpm --filter @zero/server exec vitest run src/mastra/tools/notify/__tests__/notifyUserTool.test.ts src/services/notifications/__tests__/user-slack-notifier.test.ts (existing suites green).
  • Behavioral: after a config where a deal has a linked Slack channel, the agent should DM internal prep/coaching/alerts (no slack.channelId) while still being able to post a customer-facing recap to the channel.

🤖 Generated with Claude Code

Show production surfaces and changed-file mapping

Production surfaces

Libra has not measured any production surfaces for this change yet.

Changed files → surfaces

  • apps/server/src/mastra/tools/notify/notifyUserTool.tsno production surface mapped
  • apps/server/src/mastra/utils/context-formatting/conversation-context.tsno production surface mapped