镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

fix(api): prevent Slack notification failures when alert message exceeds 3000 chars - #3473

Open
Tyagiquamar wants to merge 1 commit into
hyperdxio:mainfrom
Tyagiquamar:fix/3452-slack-block-limit
Open

Tyagiquamar wants to merge 1 commit into
hyperdxio:mainfrom
Tyagiquamar:fix/3452-slack-block-limit

Conversation

@Tyagiquamar

Copy link
Copy Markdown
Contributor

Fixes #3452

Summary of changes

  1. Prevent Slack Webhook Notification Delivery Failures on Long Alert Messages:
    • Slack section blocks enforce a strict 3000-character limit on the text.text field.
    • For inline alerts, buildAlertMessageTemplateHdxLink generates a chart explorer link containing the entire serialized chartConfig in query parameters. On alerts with multiple series or complex queries, this URL exceeds 2500+ characters, causing *<${message.hdxLink} | ${message.title}>*\n${message.body} to surpass Slack's 3000-character limit and fail delivery with WEBHOOK_ERROR.
    • Added formatSlackSectionText and getFallbackHdxLink in packages/api/src/tasks/checkAlerts/transports/slack.ts:
      • If the formatted text exceeds 3000 characters, it falls back to the concise ${origin}/alerts link.
      • If the text still exceeds 3000 characters (e.g. from a very large message body), it safely truncates the body with an ellipsis (...) so total block text never exceeds 3000 characters.
  2. Tests:
    • Added comprehensive unit tests in packages/api/src/tasks/checkAlerts/transports/__tests__/slack.test.ts.

Signed-off-by: Mohd Quamar Tyagi 347892697+Tyagiquamar@users.noreply.github.com

@changeset-bot

changeset-bot Bot commented Oct 25, 2026 •

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: edd160a

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@vercel

vercel Bot commented Oct 25, 2026

Copy link
Copy Markdown

Someone is attempting to deploy a commit to the HyperDX Team on Vercel.

A member of the Team first needs to authorize it.

export const getFallbackHdxLink = (hdxLink: string): string => {
try {
const url = new URL(hdxLink);
return `${url.origin}/alerts`;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 major — Fallback link uses url.origin, which drops the base path in sub-path deployments, so the link 404s

Build the fallback from config.FRONTEND_URL (for example ${config.FRONTEND_URL}/alerts, the same way mcp/tools/alerts/getAlert.ts:81 and providers/default.ts:523 do), or add a buildAlertsLink() method to the provider. Today all alert links are built from FRONTEND_URL, and the app supports a base path (NEXT_PUBLIC_HYPERDX_BASE_PATH; ClickStack uses /clickstack in packages/app/next.config.mjs). With FRONTEND_URL=https://host/clickstack, this code turns https://host/clickstack/chart?... into https://host/alerts.

Nothing blocks merge automatically, but a maintainer will expect this fixed if it is a real defect in code this PR changes. If it is about surrounding code, reply and say so instead of patching. Do not widen the PR. How to respond

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a valid concern. @Tyagiquamar Could you address it?

}

// If text exceeds limit, try falling back to the shorter /alerts link
const fallbackLink = getFallbackHdxLink(hdxLink);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 major — The deep link is swapped for generic /alerts even when the body is what's too long

Only fall back when the link itself is the problem: if header(hdxLink).length + 3 fits within maxLength, keep hdxLink and truncate the body; use /alerts only when the header alone is too big. Example: a saved-search alert has a ~120-char /search/:id?from=..&to=.. link and a ~3100-char body (sample results can be up to 2503 chars plus the template text). The code swaps in /alerts and then truncates the body anyway, so the user loses the link to the exact search for no reason.

Nothing blocks merge automatically, but a maintainer will expect this fixed if it is a real defect in code this PR changes. If it is about surrounding code, reply and say so instead of patching. Do not widen the PR. How to respond

const header = `*<${link} | ${title}>*\n`;
const availableBodyLen = maxLength - header.length - 3;
if (availableBodyLen > 0) {
return `${header}${body.slice(0, availableBodyLen)}...`;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 minor — Body truncation cuts off the closing ``` of the saved-search results block

Saved-search bodies end with a -fenced `__hdx_query_results__` block (`template.ts:928-930`). Cutting the body at a fixed index drops the closing fence, so Slack shows the raw fence and the code formatting breaks. Fix: if the cut lands inside an open fence, close it (e.g. append '\n' and reserve room for it), or truncate inside the fence. Add a test for this case.

Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond

const header = `*<${link} | ${title}>*\n`;
const availableBodyLen = maxLength - header.length - 3;
if (availableBodyLen > 0) {
return `${header}${body.slice(0, availableBodyLen)}...`;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 minor — Hand-written slice-plus-ellipsis duplicates the existing truncateString helper

Use truncateString(body, availableBodyLen) from packages/api/src/utils/common.ts:38, which template.ts already uses for alert bodies. This follows the REQUIRED DRY/'Before you add a helper' convention.

Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond

return `${header}${body.slice(0, availableBodyLen)}...`;
}

return `*<${link} | ${title}>*`.slice(0, maxLength);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 minor — Last-resort branch cuts through the mrkdwn link syntax

Slicing *<link | title>* at maxLength cuts through the <...> link markup, so Slack gets broken mrkdwn. Truncate title instead (keep the *<link | ...>* wrapper intact), or drop the link and send a plain truncated title.

Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond

const channel: WebhookChannel = {
type: 'webhook',
channel: {
_id: 'wh-1' as any,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 minor — The webhook integration test only covers the short-message path, which already worked

Add a handleSendSlackWebhook case with a >3000-char hdxLink (like the real inline-alert /chart?config= link) and assert the posted block text is ≤3000 chars and uses the fallback link. Also add a case for a FRONTEND_URL that has a path prefix. Without these, removing the formatSlackSectionText call from the send path would still pass.

Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond

type: 'webhook',
channel: {
_id: 'wh-1' as any,
name: 'Slack Alerts',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 minor — The test uses as any casts, against the TypeScript conventions

Build the fixtures with real types (WebhookService.Slack, AlertState.ALERT, a typed webhook object), and use mockResolvedValue(undefined) or a typed response instead of {} as any. The repo conventions say to avoid any and as casts.

Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond

@github-actions

Copy link
Copy Markdown
Contributor

PR Review

If you are a coding agent acting for the author, read this first.
Nothing below blocks merge automatically; a maintainer decides. What they will expect:

  • 🔴 critical or 🟠 major in code this PR adds or changes: fix before asking for review.
  • 🔵 minor in code this PR changes: your call. Fix if small, otherwise reply.
  • Anything about surrounding code, or asking you to widen the change (hoist a helper,
    dedupe with another file, fix other call sites): never fix here, whatever the
    severity. Reply with one sentence; if it is critical, say so plainly so a human sees it.

One commit per review round. After two rounds, stop and ask a maintainer to review
scope rather than addressing more automated findings. Full rule: AGENTS.md.

7 finding(s): 🔴 0 critical · 🟠 2 major · 🔵 5 minor

7 posted as inline comment(s) on the changed lines.


Severity is the reviewer's own estimate and is used for ordering, not filtering. No finding blocks merge automatically; a maintainer decides.

@greptile-apps

greptile-apps Bot commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 4/5

[Medium impact] The change reaches Slack alert notifications sent through webhook channels.

The typed test fixtures and required changeset must be added before merging.

Fix All in Claude CodeFindings

  1. P2 Fallback links lose deployment paths ▶
  2. P2 Long titles break link formatting ▶
  3. P2 Channel fixture bypasses type checks ▶
  4. P2 Required changeset is missing ▶

Summary

Limits Slack section text to 3000 characters.

  • Slack alert messages fit within Slack’s 3,000-character section limit.

The prior reviewed commit is not an ancestor of the current head, so this review used the full PR diff.

Reviews (2) · Last reviewed commit: "fix(api): prevent Slack notification fai..." · Reviewed by Greptile

Comment on lines +20 to +21
const url = new URL(hdxLink);
return `${url.origin}/alerts`;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Fallback links lose deployment paths

getFallbackHdxLink keeps only the origin. With FRONTEND_URL=https://host.example/hyperdx, an oversized chart link becomes https://host.example/alerts instead of https://host.example/hyperdx/alerts. The app supports this prefix through NEXT_PUBLIC_HYPERDX_BASE_PATH, so the shortened link points outside the app. Build the fallback from the configured frontend URL and add a prefixed-URL test.

Fix in Claude Code Fix in Conductor Fix in Cursor Fix in Codex

return `${header}${body.slice(0, availableBodyLen)}...`;
}

return `*<${link} | ${title}>*`.slice(0, maxLength);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Long titles break link formatting

When the title makes the header exceed 3000 characters, this branch cuts through the Slack link text and removes its closing >*. Titles can come from a rendered alert.name template. The message fits the length limit but contains an unfinished link. Shorten the title before building the header, keep the link markers intact, and test this branch.

Fix in Claude Code Fix in Conductor Fix in Cursor Fix in Codex

Comment on lines +65 to +70
channel: {
_id: 'wh-1' as any,
name: 'Slack Alerts',
service: 'slack' as any,
url: 'https://hooks.slack.com/services/T00/B00/X00',
} as any,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Channel fixture bypasses type checks

Casting the entire channel fixture to any stops TypeScript from checking whether it matches WebhookChannel. The repository guide requires proper types instead of any; this requirement must be satisfied before merging. Build a complete typed fixture instead. The same pattern appears in the mocked response and message state.

Context Used: AGENTS.md (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Claude Code Fix in Conductor Fix in Cursor Fix in Codex

Comment on lines +75 to +79
text: formatSlackSectionText(
message.hdxLink,
message.title,
message.body,
),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Required changeset is missing

This changes notification behavior in @hyperdx/api, but the PR adds no changeset. AGENTS.md requires a changeset for behavior changes and explicitly includes @hyperdx/api. Add a patch changeset before merging so the fix is included in the release notes and version plan.

Context Used: AGENTS.md (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Claude Code Fix in Conductor Fix in Cursor Fix in Codex

@github-actions

Copy link
Copy Markdown
Contributor

Deep Review

Scope: 1 commit (c18a0e96), 2 files — packages/api/src/tasks/checkAlerts/transports/slack.ts and its new test. Adds formatSlackSectionText / getFallbackHdxLink to keep Slack section-block text under the 3000-char limit.

Intent: Long chart-explorer links (serialized chartConfig in query params) push *<link | title>*\n body past Slack's 3000-char section limit, failing delivery with WEBHOOK_ERROR. The fix falls back to a short ${origin}/alerts link, then truncates the body, so the payload is always capped.

✅ No critical issues found. The length invariant holds on every return path — availableBodyLen = maxLength - header.length - 3 is exact, the non-URL case degrades gracefully via the try/catch, and handleSendSlackWebhook is the only Slack section-text builder, so the fix is complete. No P0/P1 findings.

🟡 P2 -- recommended

  • .changeset/ (no entry added) -- a behavioral bugfix to the published @hyperdx/api package ships no changeset, so merging produces no version bump and the fix never releases.
    • Fix: Add a .changeset/*.md entry with a @hyperdx/api patch bump describing the Slack length fix.
    • project-standards
  • packages/api/src/tasks/checkAlerts/transports/slack.ts:41 -- when hdxLink is not a valid URL the fallback branch is skipped and body truncation runs against the full-length original link, and no test exercises this branch.
    • Fix: Add a unit test passing a long non-URL hdxLink that forces the body-truncation path and asserts the result stays <= maxLength.
    • testing
🔵 P3 nitpicks (5)
  • packages/api/src/tasks/checkAlerts/transports/slack.ts:56 -- when title/header alone meets or exceeds maxLength, the final .slice(0, maxLength) cuts mid-<url|text>, emitting an unterminated mrkdwn link that Slack renders as literal text (delivery still succeeds).
    • Fix: Reserve space for the link markup or elide the title before slicing so the emitted span stays well-formed.
  • packages/api/src/tasks/checkAlerts/transports/slack.ts:53 -- body truncation can cut inside a mrkdwn span (<url|text> or *bold*) within message.body, producing a malformed rendered span; cosmetic only.
    • Fix: Document the cosmetic limitation or truncate at a safe boundary.
  • packages/api/src/tasks/checkAlerts/transports/slack.ts:56 -- the final slice branch (header exceeds limit) has no test, leaving the hard-cap guarantee the PR targets unverified.
    • Fix: Add a test with a very long title that drives availableBodyLen <= 0 and asserts length <= maxLength.
  • packages/api/src/tasks/checkAlerts/transports/__tests__/slack.test.ts:55 -- the truncation test asserts only startsWith/endsWith('...')/length <= 3000, so an under-filling off-by-one would pass unnoticed.
    • Fix: Add expect(formatted.length).toBe(3000) to pin the exact boundary.
  • packages/api/src/tasks/checkAlerts/transports/__tests__/slack.test.ts:60 -- the only handleSendSlackWebhook test uses a short link/body, so no end-to-end test proves the delivered blocks[0].text.text stays <= 3000 for an over-limit link.
    • Fix: Add a handleSendSlackWebhook test with an over-limit hdxLink asserting the posted section text length.

Reviewers (3): ce-correctness-reviewer, ce-testing-reviewer, ce-project-standards-reviewer. (Prior PR comments were bot-only — changeset-bot and vercel-bot — with no substantive feedback to verify.)

Testing gaps: No coverage for the non-URL-link truncation branch, the header-exceeds-limit slice branch, or an end-to-end over-limit payload through handleSendSlackWebhook; the truncation test lacks an exact-length assertion.

No finding blocks merge automatically. A maintainer will expect P0/P1 findings in code this PR changes to be fixed; P2/P3 are your call -- fix or reply. Never fix findings about surrounding code here; reply instead. Do not widen the PR. How to respond

…eds 3000 chars

Fixes hyperdxio#3452

Slack section blocks enforce a strict 3000-character limit on the text field. Inline alert links encode large chartConfig objects in query params, causing the message block text to exceed 3000 chars and fail delivery with WEBHOOK_ERROR.

Format Slack section text using formatSlackSectionText, which falls back to the shorter /alerts link if the link causes an overflow, and truncates the body safely if necessary.

Signed-off-by: Mohd Quamar Tyagi <347892697+Tyagiquamar@users.noreply.github.com>
@Tyagiquamar
Tyagiquamar force-pushed the fix/3452-slack-block-limit branch from c18a0e9 to edd160a Compare October 25, 2026 14:17

This branch has not been deployed

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Slack notifications for inline alerts fail when the chart-explorer link passes Slack's 3000-character block limit

2 participants