ci(forward-merge): notify Slack when the cascade is blocked or fails - #3123
Open
mfal wants to merge 1 commit into
Open
ci(forward-merge): notify Slack when the cascade is blocked or fails#3123mfal wants to merge 1 commit into
mfal wants to merge 1 commit into
Conversation
The forward-merge cascade escalated only as a GitHub issue, which nobody sees until they look. Post a Slack message alongside each of the three escalations: the §4 conflict, the §10 non-conflict failure, and the drift check. Both new notifications in `forward-merge.yml` key on the ESCALATION, not on the run. The §4 conflict path is the expected non-error path and exits 0, so `failure()` would never see a blocked cascade. Keying on the escalation also inherits its idempotence: `open_sync_issue`'s quiet returns and the drift check's early exits leave the output empty, so a cascade that stays blocked does not re-ping the channel on every push to `main`, and the hourly drift schedule does not repeat itself. All three steps are `continue-on-error`. Without it a webhook outage or a missing secret would turn the conflict path into a run failure, which the §10 step would then report as a genuine forward-merge failure and open an issue for. The issue stays the source of truth; Slack is the additional signal. One footgun avoided: `'\n'` in a GitHub expression is backslash-n, not a newline — single-quoted expression strings carry no escape sequences. The two dynamic messages get their newlines from `printf`, which `toJSON` escapes correctly; the static §10 payload uses a JSON `\n` directly. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Contributor
Coverage Report for ./packages/components/
File CoverageNo changed files found. |
Contributor
🚀 Preview DeploymentPreview environments are ready:
Images:
|
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.
What & why
The forward-merge cascade escalates only as a GitHub issue — which nobody sees
until they look. This adds a Slack message alongside each escalation, so a
paused or broken cascade is loud.
Three escalation points, all covered:
forward-merge.ymlhits a genuine code conflictforward-merge.ymlfails for a non-conflict reasonforward-merge-drift.ymlfindsnextbehind past the thresholdReuses the pattern and secret already in
test-visual-scheduled.yml(
slackapi/slack-github-action,SLACK_WEBHOOK_URL). ADR 0004 §4, §5 and §10are updated to match.
Two things worth a reviewer's attention
The notifications key on the escalation, not on the run. The §4 conflict is
the expected non-error path and exits 0, so a
failure()-triggerednotification would never fire on a blocked cascade — the one case we most want
to hear about. So
open_sync_issuerecords its message via a newrecord_escalationhelper and a separate step sends it.That choice also buys idempotence for free.
open_sync_issuehas three quietreturn 0s for a blockage that is already reported, and the drift check exitsearly on four conditions; none of them write the output, so no Slack message.
A cascade that stays blocked therefore does not re-ping the channel on
every push to
main, and the hourly drift schedule does not repeat itself.All three steps are
continue-on-error— and this is load-bearing, notpoliteness. Without it, a webhook outage or an unset secret would fail the §4
conflict path, which the §10 step below would then pick up via
failure()andreport as a genuine forward-merge failure, opening an issue for a problem that
does not exist. The GitHub issue stays the source of truth; Slack is the
additional signal.
Notes
'\n'inside${{ }}isbackslash-n, not a newline — single-quoted expression strings carry no escape
sequences. The two dynamic messages get real newlines from
printf, whichtoJSONescapes correctly; the static §10 payload uses a JSON\ndirectly.test-visual-scheduled.yml:57uses Markdownlink syntax
[text](url)in anmrkdwnblock. Slack does not support that —it renders literally. The correct form is
<url|text>, which the new stepsuse. Happy to fix it here or separately.
forward-merge-drift.ymlitselfstays silent — it still has no
failure()path.Verification
CI cannot exercise these paths without a real conflict, so verification was
static plus a local simulation:
run:block passesbash -n.(conflict, PR-comment refresh, drift) and the rendered text read back.
prettier --checkclean.Checklist
(
ci:→main)pnpm lintis clean (pre-push hook) — no test surface: CI workflows arenot covered by
pnpm test🤖 Generated with Claude Code