Skip to content

feat(alerts): remove legacy Slack webhook destination - #277

Open
JeremyFunk wants to merge 2 commits into
mainfrom
feat/remove-slack-webhook-alerts
Open

feat(alerts): remove legacy Slack webhook destination#277
JeremyFunk wants to merge 2 commits into
mainfrom
feat/remove-slack-webhook-alerts

Conversation

@JeremyFunk

@JeremyFunk JeremyFunk commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

What

Fully removes the legacy Slack webhook alert destination (type: "slack"). It was superseded by the "slack-bot" OAuth integration, and we validated that no customer uses the webhook variant.

The "slack-bot" destination is untouched.

Removed

  • Domain contract (packages/domain/src/http/alerts.ts): the "slack" literal in AlertDestinationType, SlackAlertDestinationConfig, UpdateSlackAlertDestinationConfig, and both union arms.
  • v2 public API (packages/domain/src/http/v2/alert-destinations.ts): the create/update arms, webhook_url/channel_label slack fields, and the OpenAPI examples + doc strings that enumerated slack (examples now use slack-bot).
  • API (apps/api): buildPublicConfig/buildSecretConfig slack arms, the create/update URL-validation branches, the slack: arm of the update Match.discriminatorsExhaustive, the { type: "slack", webhookUrl } member of DestinationSecretConfig, the v2 route toCreateRequest/toUpdateRequest cases, and the dispatchDelivery arm that POSTed to config.webhookUrl.
  • Dashboard (apps/web): the PROVIDERS.slack "Slack (webhook)" tile, its DESTINATION_TYPES entry, the webhook URL + channel-label form fields, the "Use a Slack webhook instead" fallback button in SlackBotFields, and the case "slack" create/update param builders (plus the now-dead channelLabel form field).
  • IaC (lib/alchemy-maple): the { type: "slack"; webhook_url; channel_label? } props union member, its desiredBody case and drift check; examples/README now demo a Discord destination.
  • Docs: the incoming-webhook setup section in alerting/notification-destinations.md, rewritten to describe the Slack (bot) install flow.

Back-compat for existing type='slack' rows

No DB migration — destinations store config generically (type text + config_json jsonb) — but back-compat handling is required, and is included here. Rows with type='slack' still exist in Postgres, and every read path decoded that column with a throwing sync decoder, so a single legacy row raised a defect: bare 500s on GET /alerts/destinations, the v2 list/retrieve routes and delivery history, plus a white-screened Alerts page (the web Electric collection decodes during render).

The stored type is now decoded tolerantly, following the existing safeParsePublicConfig precedent:

  • AlertsService.listDestinations skips rows whose type no longer decodes, so an undecodable row simply doesn't appear instead of failing the whole list.
  • listDeliveryEvents reuses the "webhook" fallback it already had for deleted destinations.
  • Single-row reads (hydrate/create/update) fail with a typed AlertValidationError rather than a defect.
  • Web: rowToAlertDestinationDocument returns null for such a row (skipped by the list hook), and PROVIDERS[...] lookups go through a defensive getProvider so an unknown type can never deref undefined mid-render.
  • Tests cover a stray type='slack' row on both the API list path and the web collection path.

"slack" stays removed from AlertDestinationType — this is tolerance for stray rows, not a reinstatement.

Notes for review

  • BREAKING change to the public v2 API contract. The slack arm is removed from /v2 alert-destination create/update, so an existing v2 client sending "type": "slack" now gets a validation error. Accepted by the maintainer (no customer uses the webhook variant).
  • lib/alchemy-maple now has no Slack destination support at all. It carried a "slack" arm and never had a "slack-bot" one, so IaC users lose Slack entirely rather than migrating to the bot destination. Flagged for the maintainer's judgement — intentionally not addressed in this PR.

Preserved

  • The entire "slack-bot" path.
  • All shared Slack formatting helpers — buildSlackBlocks, buildSlackBlocksFromTemplate, buildSlackFallbackText, markdownToSlackMrkdwn, slackAttachmentColor (the last is also used by alert-email.ts).
  • lib/url-validator.ts (shared with webhook/hazel/discord).
  • The template dialect string "slack" used by renderTitleBody(context, "slack", …). It was previously typed as AlertDestinationType, which no longer contains "slack", so it is now an explicit TemplateDialect = AlertDestinationType | "slack" with a comment explaining why the key must not be renamed — customers' stored notification-template overrides are keyed by it.

Verification

  • bun typecheck — turbo 33/33 green. The root tsconfig.alchemy.json step reports one pre-existing alchemy.run.ts(148) Cloudflare Worker type error, confirmed identical on a clean main.
  • bun run test --concurrency=1apps/api 1191 passed, apps/web 965 passed with only the 4 pre-existing failures in use-timezone-preference / use-recently-used-times (the known jsdom localStorage gotcha), confirmed identical on a clean main. All alert/destination suites pass. The new API regression test was verified to fail (defect) without the tolerant decode.

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

Removes the `"slack"` alert destination type — the legacy incoming-webhook
variant superseded by the `"slack-bot"` OAuth integration. No customer uses it,
and the DB stores destination config generically (`type` text + `config_json`
jsonb), so no migration is needed.

Removed: the domain create/update schemas and the type literal, the v2 wire
arms + OpenAPI examples, the API validation/build/dispatch branches, the
dashboard provider tile and webhook form fields, the alchemy IaC union member,
and the docs section.

Preserved: the `"slack-bot"` destination end to end, every shared Slack
formatting helper (buildSlackBlocks, markdownToSlackMrkdwn,
slackAttachmentColor, buildSlackFallbackText), and the `"slack"` *template
dialect* key — now typed as an explicit `TemplateDialect` so existing rules'
notification-template overrides keep working.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@pullfrog

pullfrog Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Your Pullfrog Router balance is exhausted.

You have a payment method on file but auto-reload is disabled, so runs paused once your balance went past the overdraft buffer.

Top up balance → · Enable auto-reload →

Pullfrog  | ⚠️ this action is pinned to a commit SHA, which freezes the cleanup step — switch to @v0 or keep the SHA fresh with Dependabot | Rerun failed job ➔View workflow run | via Pullfrog | Using Claude Opus𝕏

@github-actions

github-actions Bot commented Jul 29, 2026

Copy link
Copy Markdown

🍁 Maple PR preview

The preview is ready:

Service Preview URL
Web app-pr-277.maple.dev
API api-pr-277.maple.dev
Chat chat-pr-277.maple.dev
Electric Sync sync-pr-277.maple.dev

Deployed from a7209e1 · View workflow run

Removing `slack` from `AlertDestinationType` left existing
`alert_destinations` rows with `type='slack'` in Postgres, and every read
path decoded that column with a throwing sync decoder. One legacy row
raised a defect — a bare 500 on `GET /alerts/destinations`, the v2 list
and retrieve routes, and delivery history, plus a white-screened Alerts
page (the web collection decodes during render).

Decode the stored type tolerantly instead, following the existing
`safeParsePublicConfig` precedent: lists skip rows that no longer decode,
delivery history reuses the "webhook" fallback it already has for deleted
destinations, and single-row reads fail with a typed validation error
rather than a defect. On the web side the Electric row mapper returns
null for such rows (skipped by the list hook) and PROVIDERS lookups go
through a defensive `getProvider` so an unknown type can never deref
undefined mid-render.

`slack` stays removed from the union — this is back-compat for stray
rows, not a reinstatement.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@pullfrog

pullfrog Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Your Pullfrog Router balance is exhausted.

You have a payment method on file but auto-reload is disabled, so runs paused once your balance went past the overdraft buffer.

Top up balance → · Enable auto-reload →

Pullfrog  | ⚠️ this action is pinned to a commit SHA, which freezes the cleanup step — switch to @v0 or keep the SHA fresh with Dependabot | Rerun failed job ➔View workflow run | via Pullfrog | Using Claude Opus𝕏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant