Skip to content

feat: add threaded message strategy for Slack notifier#5150

Open
santiagofn wants to merge 3 commits intoprometheus:mainfrom
santiagofn:feat/support-threaded-msgs-in-slack
Open

feat: add threaded message strategy for Slack notifier#5150
santiagofn wants to merge 3 commits intoprometheus:mainfrom
santiagofn:feat/support-threaded-msgs-in-slack

Conversation

@santiagofn
Copy link
Copy Markdown

@santiagofn santiagofn commented Apr 1, 2026

Summary

Introduces a new message_strategy field for the Slack notifier, replacing and deprecating the existing update_message boolean (while keeping backward compatibility). The new field accepts three values:

  • new: default, current behavior
  • update: equivalent to the old update_message: true
  • thread: allows subsequent notifications for the same alert group to be posted as threaded replies instead of standalone messages. This reduces channel noise and keeps related alert state changes grouped together.

Builds on the arbitrary key/value data store added to the nflog in #4899, using it to persist threadTs, channelId, and transitions across restarts and HA cluster nodes.

Relates to #3221.

Commits

The changes are split into three commits for easier review:

  1. refactor: move Slack types to their own file — Separates Slack API types into their own file for better code organization. No behavior changes.
  2. refactor: move slack url creation to its own package — Extracts Slack URL resolution into its own internal package, making it reusable by the new threading logic.
  3. feat: add threaded messages as a slack option — Adds the thread message strategy with both modes, resolve actions, transition tracking, config validation, and comprehensive tests.

The thread strategy

Threading modes

Controlled by threaded_options.use_summary_header:

  • true (default) — The thread parent is a lightweight, auto-updated summary showing the alert group's transition history (e.g. "FIRING (2) → SILENCED → RESOLVED"). All alert content is posted as replies. The parent's color and title update on every notification cycle.
  • false — The first alert message itself becomes the thread parent. Subsequent alerts are threaded replies. No parent updates are performed.

Advanced threaded_options

  • resolve_emoji — Optional emoji reaction on the thread parent when all alerts resolve (requires reactions:write scope).
  • summary_header.resolve_color — Optional color override for the parent summary attachment on resolution (only valid when use_summary_header is enabled).

Configuration example

slack_configs:
  - channel: '#alerts'
    send_resolved: true
    message_strategy: thread
    threaded_options:
      use_summary_header: true
      resolve_emoji: 'white_check_mark'
      summary_header:
        resolve_color: '#AAAAAA'

Summary by CodeRabbit

  • New Features

    • Slack receivers support message_strategy ("new", "update", "thread") and threaded_options (use_summary_header, resolve_emoji, summary_header) for parent/reply threading and summary headers.
  • Documentation

    • Updated Slack docs describing message_strategy, threaded_options, and API URL requirements for update/thread modes.
  • Bug Fixes

    • More reliable thread parent tracking, resolve emoji handling, retry/persistence semantics, and stricter webhook success detection.
  • Tests

    • Added extensive tests covering strategies, threading flows, URL resolution, and retries.
  • Chores

    • Updated .gitignore to ignore IDE and plans directories.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 1, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds Slack message strategies ("new", "update", "thread"), validates strategy-specific configuration, introduces an API URL resolver, implements threaded messaging (summary-header and direct modes) with persistence and reactions, refactors notifier flow/types, and adds tests and docs.

Changes

Cohort / File(s) Summary
Gitignore & Cursor
\.cursor/.gitignore, \.gitignore
Ignore entries added: plans/ and .idea.
Slack config types & validation
config/notifiers.go, config/config_test.go, config/config.go
Add message_strategy type/constants and threaded_options types/fields; deprecate update_message compatibility; add Unmarshal/ValidateMessageStrategy checks and new strategy/threaded validation tests.
Slack testdata fixtures
config/testdata/conf.slack-*.yml
Add multiple YAML fixtures for message strategies, threaded options, resolve emoji, summary header, and global/api-url permutations.
API URL resolver
notify/slack/internal/apiurl/apiurl.go, notify/slack/internal/apiurl/apiurl_test.go
New apiurl.Resolver to derive base and method-specific Slack endpoints from api_url/api_url_file, with normalization, method path substitution, and tests for success/error cases.
Notifier types & core refactor
notify/slack/types.go, notify/slack/slack.go, notify/slack/slack_test.go
Add exported slack.Notifier, pluggable postJSONFunc, urlResolver; refactor Notify to branch by strategy, add postAndHandle/readAndParseSlackResponse, tighten webhook success detection, update nflog keys, remove file-based APIURL tests.
Threaded messaging implementation & tests
notify/slack/slack_thread.go, notify/slack/slack_thread_test.go
Implement message_strategy: "thread" with summary-header and direct modes: parent posting/updating, threaded replies, resolve emoji handling, transition-history persistence, retry semantics; add comprehensive unit and lifecycle tests.
Documentation
docs/configuration.md
Document message_strategy (`new

Sequence Diagram(s)

sequenceDiagram
    participant Notifier as Slack Notifier
    participant NFL as nflog (store)
    participant APIURL as API URL Resolver
    participant Slack as Slack API

    Notifier->>APIURL: Resolve endpoint for method (postMessage/update/reactions.add)
    APIURL-->>Notifier: Return method-specific URL
    Notifier->>NFL: Read stored thread_ts & channel
    alt no stored parent
        NFL-->>Notifier: none
        Notifier->>Slack: POST chat.postMessage (create parent or direct message)
        Slack-->>Notifier: {ok, ts, channel}
        Notifier->>NFL: Persist thread_ts, channel, transitions
        Notifier->>Slack: POST chat.postMessage (threaded reply using thread_ts) or done
        Slack-->>Notifier: Success
    else parent exists
        NFL-->>Notifier: Return thread_ts & channel
        Notifier->>Slack: POST chat.postMessage (threaded reply with thread_ts)
        Slack-->>Notifier: Success
        alt summary-header mode
            Notifier->>Slack: POST chat.update (refresh parent summary)
            Slack-->>Notifier: Success
            Notifier->>NFL: Update transitions
        end
    end
    alt resolution event
        Notifier->>Slack: POST chat.postMessage (resolved threaded reply)
        Slack-->>Notifier: Success
        Notifier->>Slack: POST reactions.add (resolve emoji)
        Slack-->>Notifier: Success
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 45.45% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main feature addition: introducing a threaded message strategy for the Slack notifier.
Description check ✅ Passed The description is comprehensive and well-structured, covering the summary, implementation details, threading modes, advanced options, configuration examples, and commit breakdown.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@santiagofn santiagofn force-pushed the feat/support-threaded-msgs-in-slack branch 4 times, most recently from 7ea82dd to 94e61c6 Compare April 1, 2026 23:00
@santiagofn santiagofn marked this pull request as ready for review April 1, 2026 23:01
@santiagofn santiagofn requested a review from a team as a code owner April 1, 2026 23:01
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@config/config_test.go`:
- Around line 1084-1086: The test fixture file
conf.slack-update-message-and-webhook.yml used by the test case with name
"update strategy with webhook URL" should be updated to validate the new
message_strategy field: replace the deprecated update_message: true entry with
message_strategy: update (or add message_strategy: update alongside removing
update_message) so the test case (file referenced by the test struct field file
and strategy `update`) exercises the new configuration key.

In `@docs/configuration.md`:
- Around line 1695-1700: Change the heading level for
`<summary_header_thread_config>` from a fifth-level heading to a fourth-level
heading (replace `#####` with `####`) so it fits under the surrounding `###
<slack_config>` section and resolves the MD001/document outline error; update
the heading line that currently reads `##### <summary_header_thread_config>` to
`#### <summary_header_thread_config>` while leaving the following YAML block and
description unchanged.

In `@notify/slack/slack_thread.go`:
- Around line 65-82: The code currently disables store writes whenever threadTs
is found even if channelId is missing, causing new parents to be posted without
persisting repaired ids; update the logic in the slack thread flow (the block
that sets req.ThreadTimestamp/req.Channel, the storeForPost assignment, and the
post-handling/read-back) to require both tsFound && chFound before setting
req.ThreadTimestamp/req.Channel and before setting storeForPost = nil; otherwise
keep storeForPost = store so postAndHandle can persist missing ids, and after
the post use the same combined condition (if !(tsFound && chFound)) when calling
store.GetStr(storeKeyThreadTs) and store.GetStr(storeKeyChannelId) to reload
repaired values.
- Around line 151-153: The thread-summary path is sending untruncated titles
(summaryTitle) which can exceed Slack's title length and cause
chat.postMessage/chat.update failures; fix by applying the same truncation used
in notify/slack/slack.go to the summary header before sending—e.g., after
building summaryTitle via buildTransitionTitle(alertName, transitions) call,
truncate it to Slack's title max length (reuse the existing truncate utility or
the exact logic from slack.go lines 89-92) and then use the truncated value
(still keep summaryColor from tmplText(n.conf.Color)). Ensure you update the
code paths that set summaryTitle in slack_thread.go (references:
buildTransitionTitle, tmplText, summaryTitle, alertName) so thread messages and
updates use the clamped title.
- Around line 146-149: The code appends a separator even when reasonToTransition
returned an empty label, producing trailing pipes like "FIRING|"; change the
logic around transitions/previousTransitions/label so you only modify
transitions when label != "" (i.e., skip writes for non-transition reasons).
Specifically, in the block that computes transitions (using variables
transitions, previousTransitions, label), only set transitions =
previousTransitions + "|" + label if both previousTransitions != "" and label !=
""; if label != "" and previousTransitions == "" set transitions = label;
otherwise leave transitions as previousTransitions (or keep it unchanged) so no
extra separators are introduced.

In `@notify/slack/slack.go`:
- Around line 270-278: The current logic in the HasThreadStrategy branch only
checks existence of storeKeyThreadTs and skips updating when it exists, which
leaves partial/stale state (threadTs without channelId) unrecovered; change the
check to fetch both store.GetStr(storeKeyThreadTs) and
store.GetStr(storeKeyChannelId) and treat the thread state as missing if either
key is absent or empty, then call store.SetStr for both storeKeyThreadTs and
storeKeyChannelId with data.Timestamp and data.Channel to overwrite incomplete
state; update the n.logger.Debug messages accordingly (referencing
n.conf.HasThreadStrategy(), store.GetStr, store.SetStr, storeKeyThreadTs,
storeKeyChannelId, data.Timestamp, data.Channel).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9aea3c8b-2205-4d35-b0a4-221e2b039daa

📥 Commits

Reviewing files that changed from the base of the PR and between 9ad4f00 and 94e61c6.

📒 Files selected for processing (17)
  • .cursor/.gitignore
  • .gitignore
  • config/config_test.go
  • config/notifiers.go
  • config/testdata/conf.slack-resolve-color-without-summary-header.yml
  • config/testdata/conf.slack-thread-message-and-webhook.yml
  • config/testdata/conf.slack-thread-resolve-emoji-without-thread-message.yml
  • config/testdata/conf.slack-thread-update-parent-without-thread-message.yml
  • config/testdata/conf.slack-update-message-and-webhook-with-new-field.yml
  • docs/configuration.md
  • notify/slack/internal/apiurl/apiurl.go
  • notify/slack/internal/apiurl/apiurl_test.go
  • notify/slack/slack.go
  • notify/slack/slack_test.go
  • notify/slack/slack_thread.go
  • notify/slack/slack_thread_test.go
  • notify/slack/types.go
💤 Files with no reviewable changes (1)
  • notify/slack/slack_test.go

@santiagofn santiagofn force-pushed the feat/support-threaded-msgs-in-slack branch from 94e61c6 to 76f63f2 Compare April 8, 2026 14:16
Signed-off-by: Santiago Fernández Núñez <santiago.nunez@nubank.com.br>
Signed-off-by: Santiago Fernández Núñez <santiago.nunez@nubank.com.br>
Made-with: Cursor
@santiagofn santiagofn force-pushed the feat/support-threaded-msgs-in-slack branch from 76f63f2 to bebf220 Compare April 8, 2026 14:22
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@config/notifiers.go`:
- Around line 645-654: The validation in the switch on c.MessageStrategy
(SlackMessageStrategyNew / SlackMessageStrategyUpdate /
SlackMessageStrategyThread) runs too early against c.APIURL and c.APIURLFile
before global defaults are applied; move or defer this URL/source validation
until after global/default merging or explicitly resolve/read both inline and
global sources first (the resolved slack API URL and its source) and then
enforce that for Update/Thread the resolved URL exists and, if it is an inline
URL, that it equals "https://slack.com/api/chat.postMessage"; update the logic
referencing c.APIURL, c.APIURLFile and c.MessageStrategy so it validates the
final resolved URL/source (not the raw receiver-level fields) to avoid rejecting
configs that inherit global.slack_api_url or use api_url_file that points to a
webhook.

In `@docs/configuration.md`:
- Around line 1665-1670: Update the documentation around message_strategy to
explicitly mention api_url_file alongside api_url: state that both api_url and
api_url_file must resolve to the exact URL
"https://slack.com/api/chat.postMessage" for the "update" and "thread"
strategies to work, and clarify that file-backed values (api_url_file) pointing
to incoming-webhook URLs will not enable update/thread behavior; refer to the
existing symbol names message_strategy, api_url, and api_url_file so the change
is added next to the current block describing "new", "update", and "thread".
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 3e1f07e7-df31-4e20-9485-da7c1d11fa5d

📥 Commits

Reviewing files that changed from the base of the PR and between 94e61c6 and 76f63f2.

📒 Files selected for processing (12)
  • config/config_test.go
  • config/notifiers.go
  • config/testdata/conf.slack-resolve-color-without-summary-header.yml
  • config/testdata/conf.slack-thread-message-and-webhook.yml
  • config/testdata/conf.slack-thread-resolve-emoji-without-thread-message.yml
  • config/testdata/conf.slack-thread-update-parent-without-thread-message.yml
  • config/testdata/conf.slack-update-message-and-webhook-with-new-field.yml
  • docs/configuration.md
  • notify/slack/slack.go
  • notify/slack/slack_thread.go
  • notify/slack/slack_thread_test.go
  • notify/slack/types.go
✅ Files skipped from review due to trivial changes (4)
  • config/testdata/conf.slack-resolve-color-without-summary-header.yml
  • config/testdata/conf.slack-thread-update-parent-without-thread-message.yml
  • config/testdata/conf.slack-thread-resolve-emoji-without-thread-message.yml
  • config/config_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • notify/slack/types.go
  • notify/slack/slack_thread_test.go

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
notify/slack/slack_thread.go (1)

262-266: Dead code: unreachable length check.

strings.Split never returns an empty slice—it returns at least [""] for an empty input. The check at lines 264-266 is unreachable. The correct handling is already at lines 284-286 where len(collapsed) == 0 catches the empty/whitespace-only case after filtering.

🧹 Suggested cleanup
 func buildTransitionTitle(transitions, alertName string) string {
 	parts := strings.Split(transitions, "|")
-	if len(parts) == 0 {
-		return alertName
-	}
 
 	type entry struct {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@notify/slack/slack_thread.go` around lines 262 - 266, In
buildTransitionTitle, remove the unreachable guard that checks len(parts) == 0
after calling strings.Split(transitions, "|") (the check around parts) because
strings.Split never returns an empty slice; rely on the existing
collapsed/filtered handling later in the function (the collapsed empty check) to
handle empty or whitespace-only inputs instead.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@notify/slack/slack_thread.go`:
- Around line 262-266: In buildTransitionTitle, remove the unreachable guard
that checks len(parts) == 0 after calling strings.Split(transitions, "|") (the
check around parts) because strings.Split never returns an empty slice; rely on
the existing collapsed/filtered handling later in the function (the collapsed
empty check) to handle empty or whitespace-only inputs instead.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7577f6ce-b985-4d63-bdc2-e32fe4b799cf

📥 Commits

Reviewing files that changed from the base of the PR and between 76f63f2 and bebf220.

📒 Files selected for processing (17)
  • .cursor/.gitignore
  • .gitignore
  • config/config_test.go
  • config/notifiers.go
  • config/testdata/conf.slack-resolve-color-without-summary-header.yml
  • config/testdata/conf.slack-thread-message-and-webhook.yml
  • config/testdata/conf.slack-thread-resolve-emoji-without-thread-message.yml
  • config/testdata/conf.slack-thread-update-parent-without-thread-message.yml
  • config/testdata/conf.slack-update-message-and-webhook-with-new-field.yml
  • docs/configuration.md
  • notify/slack/internal/apiurl/apiurl.go
  • notify/slack/internal/apiurl/apiurl_test.go
  • notify/slack/slack.go
  • notify/slack/slack_test.go
  • notify/slack/slack_thread.go
  • notify/slack/slack_thread_test.go
  • notify/slack/types.go
💤 Files with no reviewable changes (1)
  • notify/slack/slack_test.go
✅ Files skipped from review due to trivial changes (7)
  • .cursor/.gitignore
  • .gitignore
  • config/testdata/conf.slack-resolve-color-without-summary-header.yml
  • config/testdata/conf.slack-thread-resolve-emoji-without-thread-message.yml
  • config/testdata/conf.slack-thread-update-parent-without-thread-message.yml
  • config/config_test.go
  • notify/slack/slack_thread_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • notify/slack/internal/apiurl/apiurl.go
  • notify/slack/slack.go

@santiagofn santiagofn force-pushed the feat/support-threaded-msgs-in-slack branch 2 times, most recently from f66b6ff to a855ed5 Compare April 8, 2026 15:16
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 8, 2026

Caution

Review failed

An error occurred during the review process. Please try again later.

📝 Walkthrough

Walkthrough

Adds Slack delivery strategies ("new", "update", "thread"), validates strategy-specific config, introduces an API URL resolver, implements threaded messaging (summary-header and direct modes) with persistence and reactions, refactors notifier flow/types, and adds extensive tests and documentation updates.

Changes

Cohort / File(s) Summary
Gitignore & Cursor
\.cursor/.gitignore, \.gitignore
Added ignore entries: plans/ and .idea.
Slack config types & validation
config/notifiers.go, config/config_test.go, config/config.go
Added message_strategy type/constants and threaded_options types/fields; deprecated update_message compatibility; added Unmarshal/ValidateMessageStrategy checks and new table-driven tests for strategy/threaded validation.
Slack testdata fixtures
config/testdata/conf.slack-*.yml
Added multiple YAML fixtures covering message strategies, threaded options, resolve emoji, summary header and global/api-url permutations.
API URL resolver
notify/slack/internal/apiurl/apiurl.go, notify/slack/internal/apiurl/apiurl_test.go
New apiurl.Resolver to resolve base and method-specific Slack endpoints from api_url or api_url_file, with normalization, method path substitution, and tests for success/failure cases.
Notifier types & core refactor
notify/slack/types.go, notify/slack/slack.go, notify/slack/slack_test.go
Added exported slack.Notifier, pluggable postJSONFunc, urlResolver; refactored Notify to branch by strategy, introduced postAndHandle and readAndParseSlackResponse, updated nflog keys, tightened webhook success detection, and removed old file-based APIURL tests.
Threaded messaging implementation & tests
notify/slack/slack_thread.go, notify/slack/slack_thread_test.go
Implemented message_strategy: "thread" with summary-header and direct modes: parent posting/updating, threaded replies, resolve emoji handling, transition-history persistence, retry semantics; added comprehensive unit and lifecycle tests.
Documentation
docs/configuration.md
Replaced update_message boolean with message_strategy (`new

Sequence Diagram(s)

sequenceDiagram
    participant Notifier as Slack Notifier
    participant NFL as nflog (store)
    participant APIURL as API URL Resolver
    participant Slack as Slack API

    Notifier->>APIURL: Resolve endpoint for method (postMessage/update/reactions.add)
    APIURL-->>Notifier: Return method-specific URL
    Notifier->>NFL: Read stored thread_ts & channel
    alt first notification (no parent)
        NFL-->>Notifier: none
        Notifier->>Slack: POST chat.postMessage (parent if summary or parent message)
        Slack-->>Notifier: {ok, ts, channel}
        Notifier->>NFL: Persist thread_ts, channel, transitions
        Notifier->>Slack: POST chat.postMessage (threaded reply using thread_ts) or done if direct parent
        Slack-->>Notifier: Success
    else subsequent notification (parent exists)
        NFL-->>Notifier: Return thread_ts & channel
        Notifier->>Slack: POST chat.postMessage (threaded reply with thread_ts)
        Slack-->>Notifier: Success
        alt summary-header mode
            Notifier->>Slack: POST chat.update (refresh parent summary)
            Slack-->>Notifier: Success
            Notifier->>NFL: Update transitions
        end
    end
    alt resolution event
        Notifier->>Slack: POST chat.postMessage (resolved threaded reply)
        Slack-->>Notifier: Success
        Notifier->>Slack: POST reactions.add (resolve emoji)
        Slack-->>Notifier: Success
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and accurately describes the main feature addition: introducing threaded message strategy for Slack notifier, which is the primary change across all modified files.
Description check ✅ Passed The PR description comprehensively covers the new message_strategy field, its three values (new/update/thread), threading modes, advanced options, configuration examples, and references the nflog data store integration.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@santiagofn santiagofn force-pushed the feat/support-threaded-msgs-in-slack branch 3 times, most recently from c0e0526 to 2db9624 Compare April 8, 2026 15:34
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
notify/slack/slack.go (1)

53-60: ⚠️ Potential issue | 🔴 Critical

Preserve app_url as a request URL source.

New now seeds urlResolver only from c.APIURL / c.APIURLFile, but Notify always asks that resolver for the initial send URL. That drops the existing app_token / app_url path: configs that only set app_url no longer have a base URL and will fail before the first POST. Please keep c.AppURL as a first-class fallback for the initial send path.

Also applies to: 171-174

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@notify/slack/slack.go` around lines 53 - 60, The urlResolver is being created
from only c.APIURL / c.APIURLFile which drops configs that set only c.AppURL;
update the Notifier construction to seed apiurl.NewResolver with c.AppURL as the
primary fallback (e.g., include c.AppURL along with c.APIURL and c.APIURLFile
when creating urlResolver) so the initial send URL can come from AppURL, and
apply the same change where urlResolver is constructed/used in Notify (the code
around the Notify method at the 171-174 area) to preserve the app_url/app_token
path.
♻️ Duplicate comments (1)
config/notifiers.go (1)

664-675: ⚠️ Potential issue | 🟠 Major

Validate api_url_file against chat.postMessage too.

This only rejects webhook-style inline api_url. A receiver with message_strategy: update or thread and api_url_file pointing at an incoming-webhook URL still loads, but runtime then either derives invalid chat.update / reactions.add endpoints or silently falls back to plain posts because webhook responses do not return ts and channel. Please validate the resolved file-backed value against https://slack.com/api/chat.postMessage as well.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@config/notifiers.go` around lines 664 - 675, In ValidateMessageStrategy,
extend the check that currently enforces c.APIURL to also validate the resolved
value of c.APIURLFile when MessageStrategy is SlackMessageStrategyUpdate or
SlackMessageStrategyThread: if c.APIURLFile is set, read and parse its contents
into a URL (similar to how c.APIURL is represented) and ensure its String()
equals "https://slack.com/api/chat.postMessage"; otherwise return the same error
used for c.APIURL (message_strategy %q requires a bot token; api_url must be
https://slack.com/api/chat.postMessage). This update should live inside the
existing switch in ValidateMessageStrategy and reference the APIURL and
APIURLFile fields and the SlackMessageStrategyUpdate/SlackMessageStrategyThread
constants.
🧹 Nitpick comments (1)
notify/slack/slack_thread_test.go (1)

765-781: Use defer mu.Unlock() to prevent deadlock on assertion failure.

If any require assertion fails between mu.Lock() and mu.Unlock(), the mutex remains locked. While test termination typically cleans this up, it's better practice to use defer for robustness and consistency.

♻️ Suggested improvement
 	mu.Lock()
+	defer mu.Unlock()
 	require.Len(t, calls, 2, "step 1: expect 2 calls (summary parent + reply)")
 	var parentReq request
 	require.NoError(t, json.Unmarshal(calls[0].Body, &parentReq))
 	// ... assertions ...
 	t.Logf("step 1: parent summary + reply posted, parent ts=%s", parentTs)
-	mu.Unlock()

Apply similar pattern to the other locked sections at lines 792-802 and 821-840.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@notify/slack/slack_thread_test.go` around lines 765 - 781, The test holds a
mutex with mu.Lock() but calls mu.Unlock() explicitly after many require
assertions, which can deadlock if an assertion fails; change each critical
section by calling mu.Lock() immediately followed by defer mu.Unlock() (e.g.,
around the block that checks calls, unmarshals into parentReq/firstReplyReq, and
asserts parentTs/transitions) so the mutex is always released on test failure,
and apply the same defer mu.Unlock() pattern to the other locked sections that
use mu.Lock()/mu.Unlock() in this file.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@notify/slack/slack_thread_test.go`:
- Around line 695-721: The HTTP handler uses require.NoError(t, err) inside the
goroutine — change this to capture the error into a shared variable (e.g.
declare var handlerErr error above the httptest.NewServer, then inside the
http.HandlerFunc set handlerErr = err while holding mu instead of calling
require) and continue handling; after you call server.Close() in the test,
acquire mu and assert require.NoError(t, handlerErr, "HTTP handler encountered
an error"). Update references around io.ReadAll, the http.HandlerFunc, and the
capturedCall/calls slice handling to use mu when writing handlerErr.

---

Outside diff comments:
In `@notify/slack/slack.go`:
- Around line 53-60: The urlResolver is being created from only c.APIURL /
c.APIURLFile which drops configs that set only c.AppURL; update the Notifier
construction to seed apiurl.NewResolver with c.AppURL as the primary fallback
(e.g., include c.AppURL along with c.APIURL and c.APIURLFile when creating
urlResolver) so the initial send URL can come from AppURL, and apply the same
change where urlResolver is constructed/used in Notify (the code around the
Notify method at the 171-174 area) to preserve the app_url/app_token path.

---

Duplicate comments:
In `@config/notifiers.go`:
- Around line 664-675: In ValidateMessageStrategy, extend the check that
currently enforces c.APIURL to also validate the resolved value of c.APIURLFile
when MessageStrategy is SlackMessageStrategyUpdate or
SlackMessageStrategyThread: if c.APIURLFile is set, read and parse its contents
into a URL (similar to how c.APIURL is represented) and ensure its String()
equals "https://slack.com/api/chat.postMessage"; otherwise return the same error
used for c.APIURL (message_strategy %q requires a bot token; api_url must be
https://slack.com/api/chat.postMessage). This update should live inside the
existing switch in ValidateMessageStrategy and reference the APIURL and
APIURLFile fields and the SlackMessageStrategyUpdate/SlackMessageStrategyThread
constants.

---

Nitpick comments:
In `@notify/slack/slack_thread_test.go`:
- Around line 765-781: The test holds a mutex with mu.Lock() but calls
mu.Unlock() explicitly after many require assertions, which can deadlock if an
assertion fails; change each critical section by calling mu.Lock() immediately
followed by defer mu.Unlock() (e.g., around the block that checks calls,
unmarshals into parentReq/firstReplyReq, and asserts parentTs/transitions) so
the mutex is always released on test failure, and apply the same defer
mu.Unlock() pattern to the other locked sections that use mu.Lock()/mu.Unlock()
in this file.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b21e3110-2c35-4d1f-8004-cf37a344ce37

📥 Commits

Reviewing files that changed from the base of the PR and between f66b6ff and 2db9624.

📒 Files selected for processing (16)
  • config/config.go
  • config/config_test.go
  • config/notifiers.go
  • config/testdata/conf.slack-resolve-color-without-summary-header.yml
  • config/testdata/conf.slack-thread-message-and-webhook.yml
  • config/testdata/conf.slack-thread-resolve-emoji-without-thread-message.yml
  • config/testdata/conf.slack-thread-strategy-with-global-api-url.yml
  • config/testdata/conf.slack-thread-strategy-without-api-url.yml
  • config/testdata/conf.slack-thread-update-parent-without-thread-message.yml
  • config/testdata/conf.slack-update-message-and-webhook-with-new-field.yml
  • docs/configuration.md
  • notify/slack/internal/apiurl/apiurl.go
  • notify/slack/slack.go
  • notify/slack/slack_thread.go
  • notify/slack/slack_thread_test.go
  • notify/slack/types.go
✅ Files skipped from review due to trivial changes (6)
  • config/config.go
  • config/testdata/conf.slack-thread-strategy-with-global-api-url.yml
  • config/testdata/conf.slack-thread-update-parent-without-thread-message.yml
  • config/testdata/conf.slack-thread-strategy-without-api-url.yml
  • config/testdata/conf.slack-resolve-color-without-summary-header.yml
  • config/testdata/conf.slack-thread-resolve-emoji-without-thread-message.yml
🚧 Files skipped from review as they are similar to previous changes (1)
  • config/config_test.go

Signed-off-by: Santiago Fernández Núñez <santiago.nunez@nubank.com.br>
Made-with: Cursor
@santiagofn santiagofn force-pushed the feat/support-threaded-msgs-in-slack branch from 2db9624 to 8eb198f Compare April 8, 2026 16:06
@santiagofn
Copy link
Copy Markdown
Author

santiagofn commented Apr 8, 2026

Regarding 2 comments from CodeRabbit that I didn't address:

  • slack.go urlResolver / AppURL: No fix needed. The config merge in config.go line 432 already sets sc.APIURL = (*amcommoncfg.SecretURL)(sc.AppURL) when AppToken is used, so c.APIURL is populated by the time New() runs.
  • notifiers.go ValidateMessageStrategy / APIURLFile: No fix needed. Reading file contents at config validation time is inconsistent with the codebase and would break the dynamic-URL use case of api_url_file.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants