Skip to content

Commit 74004a8

Browse files
github-actions: Add Slack notifications for kernelCI failures
Notify the #linux-kernel Slack channel when the kernelCI pipeline fails on a supported release branch. Successful runs stay silent. Triggers a notification: - build / boot failures - kselftest or LTP execution infrastructure failures - kselftest regressions (>±3 test diff vs baseline) - pre-setup / matrix-setup infra failures Stays silent for: - successful runs - branches whose base is not in VALID_BASES - [skip ci] runs (skip_ci sentinel from pre-setup) - LTP regressions (intentionally not classified — LTP runs informationally per existing pipeline policy: continue-on-error on test-ltp/compare-ltp, no PR-blocking in create-pr) - LTP test failures within tolerance - kselftest pass/fail diffs within ±3 threshold - create-pr failures (avoids noise from branch-name typos, regression-induced skips, transient gh API errors) Implementation: - notify-slack-kernelci.sh follows the create-pr-body-multiarch.sh pattern: named args, lives in .github/scripts/, fetched fresh from main by the calling workflow on each run. - Posts via slackapi/slack-github-action pinned to SHA 45a88b9581bfab2566dc881e2cd66d334e621e2c (v3.0.3) using the org-wide GH_BOT_SLACK_TOKEN secret. - Channel ID stored as repo variable SLACK_CHANNEL_LINUX_KERNEL so the destination can change without code edits. - Message includes mention, failed-stage summary, and branch/commit/PR/run links. Prereqs (already in place): - vars.SLACK_CHANNEL_LINUX_KERNEL set on the repo - GH_BOT_SLACK_TOKEN org secret scoped to kernel-src-tree - Bot user is a member of #linux-kernel
1 parent 1cf6d26 commit 74004a8

2 files changed

Lines changed: 216 additions & 0 deletions

File tree

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
#!/bin/bash
2+
3+
# Builds a Slack chat.postMessage payload for a kernelCI failure and writes
4+
# it to stdout (or to a file via --output). The caller is responsible for
5+
# posting it (e.g. via slackapi/slack-github-action with a bot token).
6+
#
7+
# Usage:
8+
# notify-slack-kernelci.sh \
9+
# --channel-id CHANNEL_ID \
10+
# --base-branch BRANCH --head-ref BRANCH --head-sha SHA \
11+
# --pr-number N --is-pr true|false \
12+
# --repo OWNER/REPO --run-id ID \
13+
# --failed-stages "stage1, stage2, ..." \
14+
# [--mention-id SLACK_USER_ID] \
15+
# [--output PATH]
16+
17+
set -euo pipefail
18+
19+
CHANNEL_ID=""
20+
BASE_BRANCH=""
21+
HEAD_REF=""
22+
HEAD_SHA=""
23+
PR_NUMBER="0"
24+
IS_PR="false"
25+
REPO=""
26+
RUN_ID=""
27+
FAILED_STAGES=""
28+
MENTION_ID=""
29+
OUTPUT=""
30+
31+
# Guard so a missing flag value gives a clear error under `set -u` instead of
32+
# the opaque "unbound variable" message when we try to read $2.
33+
require_value() {
34+
if [ $# -lt 2 ]; then
35+
echo "Error: $1 requires a value" >&2
36+
exit 1
37+
fi
38+
}
39+
40+
while [[ $# -gt 0 ]]; do
41+
case "$1" in
42+
--channel-id) require_value "$@"; CHANNEL_ID="$2"; shift 2 ;;
43+
--base-branch) require_value "$@"; BASE_BRANCH="$2"; shift 2 ;;
44+
--head-ref) require_value "$@"; HEAD_REF="$2"; shift 2 ;;
45+
--head-sha) require_value "$@"; HEAD_SHA="$2"; shift 2 ;;
46+
--pr-number) require_value "$@"; PR_NUMBER="$2"; shift 2 ;;
47+
--is-pr) require_value "$@"; IS_PR="$2"; shift 2 ;;
48+
--repo) require_value "$@"; REPO="$2"; shift 2 ;;
49+
--run-id) require_value "$@"; RUN_ID="$2"; shift 2 ;;
50+
--failed-stages) require_value "$@"; FAILED_STAGES="$2"; shift 2 ;;
51+
--mention-id) require_value "$@"; MENTION_ID="$2"; shift 2 ;;
52+
--output) require_value "$@"; OUTPUT="$2"; shift 2 ;;
53+
*) echo "Error: Unknown option: $1" >&2; exit 1 ;;
54+
esac
55+
done
56+
57+
for var in CHANNEL_ID BASE_BRANCH HEAD_REF HEAD_SHA REPO RUN_ID FAILED_STAGES; do
58+
if [ -z "${!var}" ]; then
59+
# Render the var name back into the actual CLI flag (lowercase + _→-)
60+
flag="--${var,,}"
61+
flag="${flag//_/-}"
62+
echo "Error: $flag is required" >&2
63+
exit 1
64+
fi
65+
done
66+
67+
SHORT_SHA="${HEAD_SHA:0:12}"
68+
RUN_URL="https://github.com/$REPO/actions/runs/$RUN_ID"
69+
COMMIT_URL="https://github.com/$REPO/commit/$HEAD_SHA"
70+
71+
MENTION=""
72+
if [ -n "$MENTION_ID" ]; then
73+
MENTION="<@${MENTION_ID}> "
74+
fi
75+
76+
PR_LINE=""
77+
if [ "$IS_PR" = "true" ] && [ "$PR_NUMBER" != "0" ]; then
78+
PR_LINE=$'\n'"*PR:* <https://github.com/$REPO/pull/$PR_NUMBER|#$PR_NUMBER>"
79+
fi
80+
81+
MESSAGE=$(
82+
printf '%s:x: *kernelCI failed on `%s`*\n' "$MENTION" "$BASE_BRANCH"
83+
printf '*Branch:* `%s`\n' "$HEAD_REF"
84+
printf '*Commit:* <%s|%s>' "$COMMIT_URL" "$SHORT_SHA"
85+
printf '%s\n' "$PR_LINE"
86+
printf '*Failed stages:* %s\n' "$FAILED_STAGES"
87+
printf '*Run:* <%s|view logs>' "$RUN_URL"
88+
)
89+
90+
# Build chat.postMessage payload: channel + text. mrkdwn is on by default.
91+
PAYLOAD=$(jq -n \
92+
--arg channel "$CHANNEL_ID" \
93+
--arg text "$MESSAGE" \
94+
'{channel: $channel, text: $text}')
95+
96+
if [ -n "$OUTPUT" ]; then
97+
printf '%s\n' "$PAYLOAD" > "$OUTPUT"
98+
echo "Payload written to $OUTPUT" >&2
99+
else
100+
printf '%s\n' "$PAYLOAD"
101+
fi

.github/workflows/kernel-build-and-test-multiarch.yml

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1605,3 +1605,118 @@ jobs:
16051605
--body-file pr_body.md \
16061606
--label "created-by-kernelci"
16071607
fi
1608+
1609+
notify-slack:
1610+
name: Notify Slack on failure
1611+
runs-on: ubuntu-latest
1612+
# create-pr is intentionally excluded — we don't classify its result as a
1613+
# failure, so waiting for it would just delay the notification.
1614+
needs: [pre-setup, setup, build, boot, test-kselftest, test-ltp, compare-kselftest, compare-ltp]
1615+
if: always() && needs.pre-setup.outputs.skip_ci != 'true'
1616+
1617+
steps:
1618+
- name: Resolve base branch and collect failed stages
1619+
id: decide
1620+
env:
1621+
# pre-setup outputs may be empty if pre-setup itself failed before
1622+
# writing them (artifact download / checksum failure). Fall back to
1623+
# the workflow_run event payload, which is always populated.
1624+
HEAD_REF: ${{ needs.pre-setup.outputs.head_ref || github.event.workflow_run.head_branch }}
1625+
HEAD_SHA: ${{ needs.pre-setup.outputs.head_sha || github.event.workflow_run.head_sha }}
1626+
BASE_REF: ${{ needs.pre-setup.outputs.base_ref }}
1627+
KSELFTEST_BASE: ${{ needs.compare-kselftest.outputs.base_branch }}
1628+
LTP_BASE: ${{ needs.compare-ltp.outputs.base_branch }}
1629+
run: |
1630+
# Whitelist must stay in sync with compare-kselftest / compare-ltp jobs
1631+
VALID_BASES="ciqlts9_2 ciqlts9_4 ciqlts8_6 ciqlts9_6 ciq-6.12.y ciq-6.12.y-next ciq-6.18.y ciq-6.18.y-next ciqcbr7_9"
1632+
1633+
BASE_BRANCH="$KSELFTEST_BASE"
1634+
[ -z "$BASE_BRANCH" ] && BASE_BRANCH="$LTP_BASE"
1635+
[ -z "$BASE_BRANCH" ] && BASE_BRANCH="$BASE_REF"
1636+
if [ -z "$BASE_BRANCH" ] && [[ "$HEAD_REF" =~ \{[^}]+\}[_-](.+) ]]; then
1637+
BASE_BRANCH="${BASH_REMATCH[1]}"
1638+
fi
1639+
1640+
# Fail closed when base branch couldn't be resolved at all (e.g.
1641+
# pre-setup failed before emitting outputs AND head branch name
1642+
# doesn't match the extraction regex). Avoids passing an empty
1643+
# --base-branch to the script which would error out the notify job.
1644+
if [ -z "$BASE_BRANCH" ]; then
1645+
echo "Could not resolve base branch — skipping Slack notification"
1646+
echo "should_notify=false" >> $GITHUB_OUTPUT
1647+
exit 0
1648+
fi
1649+
1650+
if ! echo "$VALID_BASES" | grep -wq "$BASE_BRANCH"; then
1651+
echo "Base '$BASE_BRANCH' not in whitelist — skipping Slack notification"
1652+
echo "should_notify=false" >> $GITHUB_OUTPUT
1653+
exit 0
1654+
fi
1655+
1656+
FAILED_STAGES=()
1657+
[ "${{ needs.pre-setup.result }}" = "failure" ] && FAILED_STAGES+=("infra: pre-setup")
1658+
[ "${{ needs.setup.result }}" = "failure" ] && FAILED_STAGES+=("infra: matrix setup")
1659+
[ "${{ needs.build.result }}" = "failure" ] && FAILED_STAGES+=("build")
1660+
[ "${{ needs.boot.result }}" = "failure" ] && FAILED_STAGES+=("boot")
1661+
[ "${{ needs.test-kselftest.result }}" = "failure" ] && FAILED_STAGES+=("kselftest execution")
1662+
[ "${{ needs.test-ltp.result }}" = "failure" ] && FAILED_STAGES+=("LTP execution (infrastructure)")
1663+
1664+
[ "${{ needs.compare-kselftest.outputs.comparison_status_x86_64 }}" = "failed" ] && FAILED_STAGES+=("kselftest regression (x86_64)")
1665+
[ "${{ needs.compare-kselftest.outputs.comparison_status_aarch64 }}" = "failed" ] && FAILED_STAGES+=("kselftest regression (aarch64)")
1666+
1667+
# LTP regressions are intentionally NOT classified as failures: LTP
1668+
# runs informationally (continue-on-error in test-ltp, no PR-blocking
1669+
# in create-pr). Only LTP infra failures (test-ltp.result == failure
1670+
# above) are notified, since a crashed VM is a real CI problem.
1671+
1672+
if [ ${#FAILED_STAGES[@]} -eq 0 ]; then
1673+
echo "No failures detected — skipping Slack notification"
1674+
echo "should_notify=false" >> $GITHUB_OUTPUT
1675+
exit 0
1676+
fi
1677+
1678+
# Join with ", " — bash ${array[*]} only uses the first char of IFS,
1679+
# so set IFS to ',' and post-process the comma into ", ".
1680+
SUMMARY=$(IFS=','; echo "${FAILED_STAGES[*]}")
1681+
SUMMARY="${SUMMARY//,/, }"
1682+
echo "should_notify=true" >> $GITHUB_OUTPUT
1683+
echo "base_branch=$BASE_BRANCH" >> $GITHUB_OUTPUT
1684+
echo "head_ref=$HEAD_REF" >> $GITHUB_OUTPUT
1685+
echo "head_sha=$HEAD_SHA" >> $GITHUB_OUTPUT
1686+
echo "failed_summary=$SUMMARY" >> $GITHUB_OUTPUT
1687+
1688+
- name: Checkout kernel source
1689+
if: steps.decide.outputs.should_notify == 'true'
1690+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
1691+
with:
1692+
fetch-depth: 1
1693+
1694+
- name: Fetch notification script from main
1695+
if: steps.decide.outputs.should_notify == 'true'
1696+
run: |
1697+
git fetch origin main:main
1698+
git checkout origin/main -- .github/scripts/notify-slack-kernelci.sh
1699+
chmod +x .github/scripts/notify-slack-kernelci.sh
1700+
1701+
- name: Build Slack payload
1702+
if: steps.decide.outputs.should_notify == 'true'
1703+
run: |
1704+
.github/scripts/notify-slack-kernelci.sh \
1705+
--channel-id "${{ vars.SLACK_CHANNEL_LINUX_KERNEL }}" \
1706+
--base-branch "${{ steps.decide.outputs.base_branch }}" \
1707+
--head-ref "${{ steps.decide.outputs.head_ref }}" \
1708+
--head-sha "${{ steps.decide.outputs.head_sha }}" \
1709+
--pr-number "${{ needs.pre-setup.outputs.pr_number || '0' }}" \
1710+
--is-pr "${{ needs.pre-setup.outputs.is_pr || 'false' }}" \
1711+
--repo "${{ github.repository }}" \
1712+
--run-id "${{ github.run_id }}" \
1713+
--failed-stages "${{ steps.decide.outputs.failed_summary }}" \
1714+
--output slack_payload.json
1715+
1716+
- name: Post to Slack
1717+
if: steps.decide.outputs.should_notify == 'true'
1718+
uses: slackapi/slack-github-action@45a88b9581bfab2566dc881e2cd66d334e621e2c # v3.0.3
1719+
with:
1720+
method: chat.postMessage
1721+
token: ${{ secrets.GH_BOT_SLACK_TOKEN }}
1722+
payload-file-path: slack_payload.json

0 commit comments

Comments
 (0)