One Slack thread per breakage episode with live status digest - #84
Merged
Conversation
After posting failure replies and the status reply, fetch the thread's status replies (conversations.replies) and recompute the parent message text via chat.update, refreshing the parent metadata status (failing/resolved) so later finds see episode state. - Messenger#digest renders latest-run job statuses deterministically for any report ordering; Messenger#resolved? reports all-green runs - Reports without run_id sort as the oldest run - TestEnvironment::Github#run_id falls back to GITHUB_RUN_ID and no longer raises when NOTIFY_TEST_RUN_ID is unset
GitHub's re-run failed jobs reuses GITHUB_RUN_ID, so a re-run pass posted the same run_id and lost to the original failure in the digest, leaving the episode unresolvable. Add TestEnvironment#run_key (run id plus GITHUB_RUN_ATTEMPT when present) and use it for the status payload's run_id field. test_run_url keeps the bare run_id, and the persisted payload field name is unchanged so old threads still parse.
- Raise the conversations.replies limit to 1000 (API max) and warn via the logger when has_more indicates truncation, since the digest may then be stale for very long threads - Cover the non-ok replies fetch: no chat.update is attempted - Document the accepted premature-resolve race on update_parent (fetch/update are not atomic; the next event repairs a lost write)
NOTIFY_THREAD_STORE=none forces the Null store even when a bot token would otherwise select SlackMetadata. ChatNotifier.call accepts a thread_store: kwarg and assigns it onto each chatter via the existing writer, leaving Chatter.handling's signature unchanged. Also pins two Task 8 review carry-forwards: webhook-only chatters stay silent on non-verbose success, and the success-silence paths actually consult the store.
Added: One Slack thread per breakage episode on a branch/PR, shared across parallel CI jobs, with the parent message maintained as a live status digest Added: Passing runs resolve open failure episodes, updating the thread parent to resolved Version: minor
The digest embedded writer-dependent state: identifier carried the writing job's ruby version, and the unresolved prefix used the writer's message_prefix, so concurrent writers produced different parent texts. Build the digest header from shared state only (app, sha, branch) and derive the prefix from the reports, so any writer converges on identical text. The convergence test now permutes writers (success and failure messengers with different ruby versions) as well as report order. Also document the two-frameworks-in-one-job caveat in the README (set NOTIFY_JOB_NAME per framework).
Fixed: Notifications no longer fail silently: branch and sha now resolve from CI env or git instead of raising on the app name Version: minor
CI checks out a detached HEAD, so `git branch --show-current` returns empty and the ambient-git test failed. App now takes an injectable runner (defaulting to backticks) and the fallback tests script it. Also capture the intentionally-logged Slack error in the parent-failure test so suite output stays pristine.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements Phase 1's remaining threading work: one Slack thread per breakage episode on a branch/PR, shared across parallel CI jobs, with the parent message maintained as a live status digest that resolves automatically when the branch goes green.
Design doc and implementation plan were developed and reviewed incrementally; every change was test-driven.
How it works
ThreadStorestrategy (DI, like every collaborator): maps a thread key (app#pr-ref-or-branch) to a thread.Null(default, zero-config) andSlackMetadata— which uses Slack itself as the store: parents carry message metadata, jobs find the episode viaconversations.history. No shared storage or infrastructure.chat.update(test ruby-3.2 ❌ 12 · test ruby-3.3 ✅). Concurrent writers converge — the digest is a pure function of the reports (property-tested across writers × orderings).GITHUB_RUN_ATTEMPTis folded into run keys so "Re-run failed jobs" supersedes earlier attempts.Also fixes a silent production bug
Messengerdelegatesbranch/shatoapp, whichChatNotifier.callpassed as a plain String — every real notification raisedNoMethodError, swallowed by the rescue-and-log incall. NewChatNotifier::Appvalue object resolves branch/sha fromNOTIFY_BRANCH/GITHUB_*env or git, with a regression test through the real factories.Configuration
NOTIFY_THREAD_STORE=noneNOTIFY_JOB_NAMEGITHUB_JOB+ ruby version)ChatNotifier.call(thread_store:)channels:history(+groups:historyfor private channels); channel ID requiredTest plan
rake test— 108 runs, 209 assertions, 0 failures (67 new tests)standardrb— no new offenses (7 pre-existing on main untouched)rake reissue:preview— 0.4.0, changelog entries from commit trailers