Fix/webhook cascade#2591
Conversation
…webhook-cascade
…webhook-cascade
🦋 Changeset detectedLatest commit: 02258cc The changes in this PR will be included in the next version bump. This PR includes changesets to release 3 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 |
|
@Aryainguz is attempting to deploy a commit to the HyperDX Team on Vercel. A member of the Team first needs to authorize it. |
Greptile SummaryThis PR closes a silent failure mode where deleting a webhook left every alert whose
Confidence Score: 5/5Safe to merge — the cascade correctly nulls referencing alert channels within the same team before removing the webhook, and the alert evaluation path handles a null channel type gracefully without throwing errors. Both the updateMany and findOneAndDelete operations filter by team, preserving cross-team isolation. The alert runtime (renderAlertTemplate) already handles channel.type === null by returning null from getDefaultExternalAction and skipping any delivery — no new error surface is introduced. The only open item is a minor test fixture inconsistency (source: 'logs') that has no runtime impact. No files require special attention. The test fixture uses a non-enum source value but this does not affect correctness. Important Files Changed
Sequence Diagram%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant Client
participant WebhookRouter as DELETE /webhooks/:id
participant AlertModel as Alert.updateMany
participant WebhookModel as Webhook.findOneAndDelete
Client->>WebhookRouter: DELETE /webhooks/:id
WebhookRouter->>WebhookRouter: Verify teamId (403 if null)
WebhookRouter->>AlertModel: "updateMany({ channel.webhookId: id, team: teamId }, { $set: { channel: { type: null } } })"
AlertModel-->>WebhookRouter: alerts updated (channel nulled)
WebhookRouter->>WebhookModel: "findOneAndDelete({ _id: id, team: teamId })"
WebhookModel-->>WebhookRouter: webhook removed
WebhookRouter-->>Client: "200 {}"
Note over AlertModel,WebhookModel: Two separate operations — not wrapped in a transaction
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant Client
participant WebhookRouter as DELETE /webhooks/:id
participant AlertModel as Alert.updateMany
participant WebhookModel as Webhook.findOneAndDelete
Client->>WebhookRouter: DELETE /webhooks/:id
WebhookRouter->>WebhookRouter: Verify teamId (403 if null)
WebhookRouter->>AlertModel: "updateMany({ channel.webhookId: id, team: teamId }, { $set: { channel: { type: null } } })"
AlertModel-->>WebhookRouter: alerts updated (channel nulled)
WebhookRouter->>WebhookModel: "findOneAndDelete({ _id: id, team: teamId })"
WebhookModel-->>WebhookRouter: webhook removed
WebhookRouter-->>Client: "200 {}"
Note over AlertModel,WebhookModel: Two separate operations — not wrapped in a transaction
Reviews (2): Last reviewed commit: "test: fix missing required threshold fie..." | Re-trigger Greptile |
Deep ReviewThis PR nulls the ✅ No critical issues found. 🟡 P2 — recommended
🔵 P3 nitpicks (5)
Reviewers (10): correctness, testing, maintainability, project-standards, security, reliability, kieran-typescript, adversarial, agent-native, learnings. Testing gaps: No cross-team isolation test for the cascade; |
|
Thanks for taking a look at this @Aryainguz
This is indeed a problem, but IMO this change makes it slightly worse than before because the user can still silently kill many alerts, but now the alert history UI does not indicate any problem to the user. As the alert task is currently implemented, alerts with a broken Webhook reference will still be evaluated and save their state to the AlertHistory. So the alerts haven't died, their state is just shadowed in the AlertHistory UX with the webhook error. So I think that a better approach here might be to either:
|
Summary
Deleting a webhook left every alert whose
channel.webhookIdpointed at it broken indefinitely silently loggingWEBHOOK_ERRORon every evaluation tick without any user-visible notice.A user can delete a webhook (to rotate credentials, rename it, etc.) not realising that every alert attached to it just died silently.
This fix nulls the
channelfield on all referencing alerts before the webhook is removed, so they continue to evaluate but no longer attempt delivery to the deleted endpoint. The alert data, thresholds, and history are fully preserved.Dashboard deletion cascades via
deleteDashboardAlerts; SavedSearch deletion viadeleteSavedSearchAlerts. Webhook deletion had no equivalent this PR closes that gap.Fixes #2590
Changed file:
packages/api/src/routers/api/webhooks.tsChanges:
packages/api/src/routers/api/webhooks.ts: AddedAlert.updateManyinside theDELETE /:idhandler to cascade-null the channel of all attached alerts.packages/api/src/routers/api/__tests__/webhooks.test.ts: Added an integration test verifying the cascade correctly nulls the alert channel in the database when the webhook is deleted./.changeset/fix-webhook-cascade-delete.mdLocal Validations