Libra CodeHub

CedarCopilot/cedar-mail

Branch: staging

feat(slack): transcribe and reply to voice notes sent to Cedar's DM

merged#2680CedarCopilot

CedarCopilot wants to merge 2 commits into staging from feat/slack-voice-notes

Live on prod, no production signal yetTimeline and evidence
  1. Opened
    Sep 3, 2026, 8:44 PM
  2. Merged
    Sep 3, 2026, 9:10 PM
  3. Live on prod
    Sep 3, 2026, 9:10 PM
  4. Observed 18 hours
    Sep 3, 2026, 9:10 PM
  5. Unobserved

    Live on prod, no production signal yet

    Sep 3, 2026, 9:10 PM
  6. Pipelines steady after this deploy
    Sep 3, 2026, 9:10 PM
  7. Sep 3, 2026, 9:12 PM

Behaviors Libra is checking

Slack file downloads enforce their caller-specific byte limit during streaming, cancel oversized responses immediately, and return a too_large skip result instead of materializing the entire file in API memory.Not checked
prod
Token-resolution failures, missing download URLs, Slack download errors, missing Groq configuration, Groq errors, and empty transcripts are contained and produce a retry-or-type-instead reply rather than an uncaught webhook rejection.Not checked
prod
If Cedar cannot access a Slack voice note because the connection lacks the required user file scope, it tells the user to reconnect Slack instead of silently dropping the note.Not checked
prod
A voice note in a thread where Cedar has never previously participated is ignored without transcription, an echo, or an unsolicited Cedar reply, while eligible DMs and existing Cedar threads continue to work.Not checked
prod
When a Slack message contains both text and an audio attachment, Cedar uses the cleaned text as the query and does not download or transcribe the audio.Not checked
prod
A Slack voice note sent as a message in Cedar's DM or an existing Cedar thread is downloaded with the user's Slack token, transcribed with Groq Whisper, and passed to the chat agent when the message has no text.Not checked
prod

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

Summary

  • Slack DM/thread-reply chat (handleSlackBotInteraction) derived its query from event.text alone, so a voice note (empty text, audio in files) was silently dropped -- no reply, no log. Verified against a real voice note sent during this investigation.
  • Adds files to SlackMessageEvent, and downloads + transcribes the audio (Groq whisper-large-v3-turbo, same model already live in the mail composer's voice-to-draft feature) when text is empty, echoing the transcript before the agent runs.
  • Extracted two shared helpers rather than duplicating logic: fetchSlackFileBytes (out of slack-files.ts's existing image-download path) and transcribeAudioBuffer (out of the mail composer's tRPC route). Both are now used by two callers instead of one each.
  • Missing files:read scope or a download/transcription failure gets a plain-language reply ("reconnect Slack..." / "mind trying again...") instead of silence.

Caught in review

Thermo-review found a real ordering bug before merge: the new branch posted to Slack (the echo, or a failure reply) before the requireExistingBotThread gate ran. A voice note dropped as a reply into any thread Cedar had never spoken in -- not just DMs -- would have gotten an unsolicited Cedar reply, bypassing the exact guard that gate exists to enforce. Fixed by moving the branch to after the gate. Full review in apps/server/docs/slack-voice-notes.md.

Test plan

  • pnpm --filter @zero/server run types -- clean
  • pnpm deps:check -- no violations
  • Full Slack + trpc-routes vitest suite (66 files / 784 tests) -- all passing, no regressions from the fetchSlackFileBytes/transcribeAudioBuffer extractions
  • 13 new unit tests for findVoiceNoteFile / transcribeSlackVoiceNote (happy path, missing scope, download failure, missing API key, Groq error, empty transcript)
  • Live, real-account verification (not mocked): downloaded and transcribed the real audio file via the shipped transcribeSlackVoiceNote, then ran the full handleSlackBotInteraction against a synthetic event pointed at the real Cedar Mail DM -- Cedar posted the transcript echo and a live agent reply in Slack. Also incidentally verified the failure path posts a graceful message rather than crashing (surfaced by a misconfigured first test run).

Design doc

apps/server/docs/slack-voice-notes.md -- current state, investigation findings against the real message, proposed changes, phased plan, and post-review implementation notes.

šŸ¤– Generated with Claude Code

https://claude.ai/code/session_01VwETaLnBZrY3bYYTEWMFR3

Greptile Summary

The PR adds Slack voice-note support by downloading audio attachments, transcribing them through a shared Groq helper, echoing the transcript, and passing it into the existing chat agent. It also extracts shared Slack-file download and transcription code and adds focused tests.

  • Extends Slack message events with file metadata and recognizes audio attachments.
  • Adds user-facing responses for missing file scope and transcription failures.
  • Reuses the extracted download helper for attachment storage and the extracted Groq helper for composer transcription.
  • Adds voice-note helper tests and a design/verification document.

Confidence Score: 2/5

The PR should not merge until oversized downloads are bounded before buffering and credential or download rejections reliably produce the intended Slack failure reply.

The new interaction path can allocate an entire oversized attachment in the shared API process and can silently lose voice-note interactions when credential resolution or Slack downloading rejects outside the transcription catch.

Files Needing Attention: apps/server/src/services/integrations/slack/slack-files.ts, apps/server/src/mastra/routeHandlers/slack/slack-bot-chat.ts, apps/server/src/mastra/routeHandlers/slack/tests/slack-voice-note.test.ts

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/docs/slack-voice-notes.mdno production surface mapped
  • apps/server/src/mastra/routeHandlers/slack/__tests__/slack-voice-note.test.tsno production surface mapped
  • apps/server/src/mastra/routeHandlers/slack/slack-bot-chat.tsno production surface mapped
  • apps/server/src/services/integrations/slack/__tests__/slack-files-inbound.test.tsno production surface mapped
  • apps/server/src/services/integrations/slack/slack-events-webhook.tsno production surface mapped
  • apps/server/src/services/integrations/slack/slack-files.tsno production surface mapped
  • apps/server/src/services/transcription/groq-whisper.tsno production surface mapped
  • apps/server/src/trpc/routes/transcription.tsno production surface mapped