Add pluggable inbound-webhook framework + Granola meeting summarizer#4
Open
Miyamura80 wants to merge 2 commits into
Open
Add pluggable inbound-webhook framework + Granola meeting summarizer#4Miyamura80 wants to merge 2 commits into
Miyamura80 wants to merge 2 commits into
Conversation
Introduces webhooks/ as a place to mount custom webhook listeners on the same Bun.serve instance. First source is Granola: when a meeting wraps, the emitter POSTs to /api/webhooks/granola and a Codex-generated summary is streamed into the configured Slack channel. - Shared X-Webhook-Secret auth and Redis NX/EX dedup per source - Handler returns 200 immediately and processes in the background - Failures post a⚠️ notice to the same channel so issues are visible - Extracted streamCodex into lib/codex.ts so mentions and webhooks share it
Qodo reviews are paused for this user.Troubleshooting steps vary by plan Learn more → On a Teams plan? Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center? |
There was a problem hiding this comment.
3 issues found across 6 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="webhooks/granola.ts">
<violation number="1" location="webhooks/granola.ts:42">
P2: Validate payload types before property access; non-object JSON can currently throw and return 500.</violation>
<violation number="2" location="webhooks/granola.ts:65">
P2: Normalize `attendees` to an array before joining; current code can throw before the catch block.</violation>
</file>
<file name="lib/codex.ts">
<violation number="1" location="lib/codex.ts:35">
P2: Validate the Codex subprocess exit code before returning; non-zero exits are currently treated as success.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
Fix all with cubic | Re-trigger cubic
Addresses three review issues from cubic on PR #4: - webhooks/granola.ts: reject non-object JSON bodies (null, arrays, primitives) before field access, instead of letting a TypeError bubble up as a 500. - webhooks/granola.ts: normalize `attendees` with Array.isArray before calling .join, so a malformed (non-array, non-empty) value can no longer throw outside the surrounding try/catch in the background task. - lib/codex.ts: await the subprocess and throw on non-zero exit so silent codex failures surface as errors instead of empty summaries. Callers (webhook handler, onNewMention) now see the failure and can react.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Introduces a small framework in
webhooks/for mounting custom inbound webhooks on the existingBun.serve()instance, and ships the first source: a Granola "meeting finished" handler that streams a Codex summary into Slack.(ctx: { bot }) => { path, handler }exported fromwebhooks/<name>.ts.index.tsmounts every factory underBun.serve({ routes }). Adding a new source = drop a file + one line inindex.ts.CLAUDE.md):X-Webhook-Secret: $WEBHOOK_SECRET.SET key NX EX 604800(7-day TTL) — retried deliveries return 200 without re-processing.:warning:notice to the same Slack channel so issues are visible instead of silently swallowed.webhooks/granola.ts):POST /api/webhooks/granolaaccepts{ meeting_id, title, transcript, notes?, attendees? }, asks Codex for a Slack-formatted wrap-up (header + 3-5 bullets + optional action items), and streams it intoGRANOLA_SLACK_CHANNEL_ID(intended:#edison-os-updates) viabot.channel("slack:Cxxx").post(streamCodex(...)).streamCodexout ofindex.tsintolib/codex.tssoonNewMentionand webhook handlers share one implementation.Files
webhooks/types.ts—WebhookContext,WebhookRoute,WebhookFactorywebhooks/granola.ts— Granola handlerlib/codex.ts— sharedstreamCodexindex.ts— wires the webhooks array intoBun.serve({ routes }).env.example— addsWEBHOOK_SECRETandGRANOLA_SLACK_CHANNEL_IDCLAUDE.md— documents the inbound-webhook conventionsBefore testing
.env:brew services start redis).bun run index.ts. Startup logs should showPOST /api/webhooks/granolamounted.Test plan
POST /api/webhooks/slackandPOST /api/webhooks/granola.X-Webhook-Secretreturns401:200immediately and a streamed wrap-up lands in#edison-os-updatesa few seconds later:meeting_idreturns200 Duplicate (already processed)and does not post a second message.400.GRANOLA_SLACK_CHANNEL_IDto an ID the bot can't post to) and confirm a:warning:failure notice attempt is logged.bunx tsc --noEmitis clean (already verified locally).@mentionflow still streams a Codex reply (regression check on thestreamCodexextraction).Generated by Claude Code
Summary by cubic
Adds a pluggable inbound-webhook framework and the first source: a Granola “meeting finished” webhook that streams a Codex summary to Slack. Extracts Codex streaming to
lib/codex.ts, mounts webhooks on the existing server, and hardens JSON validation and Codex exit handling to prevent silent failures.New Features
webhooks/via a simple factory andBun.serveroutes.X-Webhook-Secretauth, RedisSET NX EX 7ddedup, 200 immediate with background processing, and:warning:failure notices.POST /api/webhooks/granolaaccepts{meeting_id, title, transcript, notes?, attendees?}and posts a Slack-formatted wrap-up toGRANOLA_SLACK_CHANNEL_ID.Bug Fixes
attendeeswithArray.isArraybefore.join.lib/codex.ts, await the subprocess and throw on non-zero exit so errors surface instead of producing empty summaries.Written for commit dc6d91c. Summary will update on new commits. Review in cubic