ChatNotifier

Notify a chat room with data from your test run.

Installation

gem "chat_notifier", git: "https://github.com/SOFware/chat_notifier.git"

Usage

Minitest

Your minitest suite should pick up the formatter automatically.

RSpec

Add to your spec_helper.rb or rails_helper.rb:

require "chat_notifier/rspec_formatter"

config.add_formatter "ChatNotifier::RspecFormatter" if ENV["CI"]

Add to your config/application.rb within your namespaced module

  def self.sha
    `git rev-parse --short HEAD`.chomp
  end

  def self.branch
    `git branch --show-current`.chomp
  end

Add these variables to your env files

      NOTIFY_SLACK_WEBHOOK_URL
      NOTIFY_SLACK_NOTIFY_CHANNEL
      NOTIFY_CURRENT_REPOSITORY_URL
      NOTIFY_TEST_RUN_ID

If you are not using Rails, you will need to add this ENV variable:

      NOTIFY_APP_NAME

Threaded failure notifications (optional)

Incoming webhooks post a single message. If you instead provide a Slack bot token, ChatNotifier uses the Slack Web API and threads failures: it posts the branch/run summary as a parent message, then posts the failing files — grouped into reasonably sized batches — as threaded replies.

      NOTIFY_SLACK_BOT_TOKEN        # xoxb-… bot token; enables threading
      NOTIFY_SLACK_NOTIFY_CHANNEL   # channel ID (C…) — required for episode threading; a #name only posts
      NOTIFY_SLACK_THREAD_GROUP_SIZE # optional, files per reply (default 10)

When both NOTIFY_SLACK_BOT_TOKEN and NOTIFY_SLACK_WEBHOOK_URL are set, the bot token is preferred. The bot needs the chat:write scope.

Episode threading (bot token only)

With a bot token, ChatNotifier keeps one thread per breakage episode on a branch/PR — shared across parallel CI jobs (matrix builds). Parent messages carry message metadata; each job finds the episode thread by scanning channel history, so no shared storage is needed. The parent is a live status digest updated as each job reports (e.g. test ruby-3.2 ❌ 12 · test ruby-3.3 ✅) and flips to ✅ resolved when the latest run is all green. Passing runs post nothing unless they resolve a known open episode.

      NOTIFY_THREAD_STORE           # set to "none" to opt out of episode threading
      NOTIFY_JOB_NAME               # optional job identity (default: GITHUB_JOB + ruby version)

GITHUB_RUN_ATTEMPT is picked up automatically, so "Re-run failed jobs" supersedes earlier attempts in the digest.

In addition to chat:write, the bot needs channels:history (and groups:history for private channels) to find episode threads, and it must be a member of the channel (/invite @YourBot). Use the channel ID in NOTIFY_SLACK_NOTIFY_CHANNEL: posting accepts a #name, but the history lookup does not, so a name degrades to per-job threads. Missing scopes or membership degrade gracefully: the lookup logs an error and each job falls back to its own thread.

Caveats: verbose mode (NOTIFIER_VERBOSE) posts plain messages and bypasses episode resolution, and a thread store passed programmatically (ChatNotifier.call(thread_store: ...)) takes precedence over NOTIFY_THREAD_STORE=none. A single CI job running both RSpec and Minitest posts two status reports under the same job identity and only the earliest per run counts in the digest — set NOTIFY_JOB_NAME per framework to disambiguate.

Debug your Slack setup

Create rake task to test the connection to your Slack channel

namespace :chat_notifier do
  desc "Tests chat notifier"
  task debug: :environment do
    unless ENV["NOTIFY_SLACK_WEBHOOK_URL"]
      puts "You MUST set the environment variables for:\nNOTIFY_SLACK_WEBHOOK_URL"
      return
    end
    ENV["DEBUG"] = "1"
    ENV["NOTIFY_CURRENT_REPOSITORY_URL"] = "https://example.com"
    ENV["NOTIFY_TEST_RUN_ID"] = "9999"
    ENV["NOTIFY_APP_NAME"] = "Example App" # Defaults to your Rails app name
    require "chat_notifier"

    failure = ChatNotifier::DebugExceptionLocation.new(location: "fake/path.rb")
    summary = ChatNotifier::DebugSummary.new(failed_examples: [failure])

    ChatNotifier.debug!(ENV, summary:)
  end
end

Contributing

This gem is managed with Reissue. Releases are automated via the shared release workflow. Trigger a release by running the "Release gem to RubyGems.org" workflow from the Actions tab.