Skip to content

Commit a34aeb4

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 a34aeb4

2 files changed

Lines changed: 197 additions & 0 deletions

File tree

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
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+
while [[ $# -gt 0 ]]; do
32+
case "$1" in
33+
--channel-id) CHANNEL_ID="$2"; shift 2 ;;
34+
--base-branch) BASE_BRANCH="$2"; shift 2 ;;
35+
--head-ref) HEAD_REF="$2"; shift 2 ;;
36+
--head-sha) HEAD_SHA="$2"; shift 2 ;;
37+
--pr-number) PR_NUMBER="$2"; shift 2 ;;
38+
--is-pr) IS_PR="$2"; shift 2 ;;
39+
--repo) REPO="$2"; shift 2 ;;
40+
--run-id) RUN_ID="$2"; shift 2 ;;
41+
--failed-stages) FAILED_STAGES="$2"; shift 2 ;;
42+
--mention-id) MENTION_ID="$2"; shift 2 ;;
43+
--output) OUTPUT="$2"; shift 2 ;;
44+
*) echo "Error: Unknown option: $1" >&2; exit 1 ;;
45+
esac
46+
done
47+
48+
for var in CHANNEL_ID BASE_BRANCH HEAD_REF HEAD_SHA REPO RUN_ID FAILED_STAGES; do
49+
if [ -z "${!var}" ]; then
50+
# Render the var name back into the actual CLI flag (lowercase + _→-)
51+
flag="--${var,,}"
52+
flag="${flag//_/-}"
53+
echo "Error: $flag is required" >&2
54+
exit 1
55+
fi
56+
done
57+
58+
SHORT_SHA="${HEAD_SHA:0:12}"
59+
RUN_URL="https://github.com/$REPO/actions/runs/$RUN_ID"
60+
COMMIT_URL="https://github.com/$REPO/commit/$HEAD_SHA"
61+
62+
MENTION=""
63+
if [ -n "$MENTION_ID" ]; then
64+
MENTION="<@${MENTION_ID}> "
65+
fi
66+
67+
PR_LINE=""
68+
if [ "$IS_PR" = "true" ] && [ "$PR_NUMBER" != "0" ]; then
69+
PR_LINE=$'\n'"*PR:* <https://github.com/$REPO/pull/$PR_NUMBER|#$PR_NUMBER>"
70+
fi
71+
72+
MESSAGE=$(
73+
printf '%s:x: *kernelCI failed on `%s`*\n' "$MENTION" "$BASE_BRANCH"
74+
printf '*Branch:* `%s`\n' "$HEAD_REF"
75+
printf '*Commit:* <%s|%s>' "$COMMIT_URL" "$SHORT_SHA"
76+
printf '%s\n' "$PR_LINE"
77+
printf '*Failed stages:* %s\n' "$FAILED_STAGES"
78+
printf '*Run:* <%s|view logs>' "$RUN_URL"
79+
)
80+
81+
# Build chat.postMessage payload: channel + text. mrkdwn is on by default.
82+
PAYLOAD=$(jq -n \
83+
--arg channel "$CHANNEL_ID" \
84+
--arg text "$MESSAGE" \
85+
'{channel: $channel, text: $text}')
86+
87+
if [ -n "$OUTPUT" ]; then
88+
printf '%s\n' "$PAYLOAD" > "$OUTPUT"
89+
echo "Payload written to $OUTPUT" >&2
90+
else
91+
printf '%s\n' "$PAYLOAD"
92+
fi

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

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1605,3 +1605,108 @@ 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+
if ! echo "$VALID_BASES" | grep -wq "$BASE_BRANCH"; then
1641+
echo "Base '$BASE_BRANCH' not in whitelist — skipping Slack notification"
1642+
echo "should_notify=false" >> $GITHUB_OUTPUT
1643+
exit 0
1644+
fi
1645+
1646+
FAILED_STAGES=()
1647+
[ "${{ needs.pre-setup.result }}" = "failure" ] && FAILED_STAGES+=("infra: pre-setup")
1648+
[ "${{ needs.setup.result }}" = "failure" ] && FAILED_STAGES+=("infra: matrix setup")
1649+
[ "${{ needs.build.result }}" = "failure" ] && FAILED_STAGES+=("build")
1650+
[ "${{ needs.boot.result }}" = "failure" ] && FAILED_STAGES+=("boot")
1651+
[ "${{ needs.test-kselftest.result }}" = "failure" ] && FAILED_STAGES+=("kselftest execution")
1652+
[ "${{ needs.test-ltp.result }}" = "failure" ] && FAILED_STAGES+=("LTP execution (infrastructure)")
1653+
1654+
[ "${{ needs.compare-kselftest.outputs.comparison_status_x86_64 }}" = "failed" ] && FAILED_STAGES+=("kselftest regression (x86_64)")
1655+
[ "${{ needs.compare-kselftest.outputs.comparison_status_aarch64 }}" = "failed" ] && FAILED_STAGES+=("kselftest regression (aarch64)")
1656+
1657+
# LTP regressions are intentionally NOT classified as failures: LTP
1658+
# runs informationally (continue-on-error in test-ltp, no PR-blocking
1659+
# in create-pr). Only LTP infra failures (test-ltp.result == failure
1660+
# above) are notified, since a crashed VM is a real CI problem.
1661+
1662+
if [ ${#FAILED_STAGES[@]} -eq 0 ]; then
1663+
echo "No failures detected — skipping Slack notification"
1664+
echo "should_notify=false" >> $GITHUB_OUTPUT
1665+
exit 0
1666+
fi
1667+
1668+
# Join with ", " — bash ${array[*]} only uses the first char of IFS,
1669+
# so set IFS to ',' and post-process the comma into ", ".
1670+
SUMMARY=$(IFS=','; echo "${FAILED_STAGES[*]}")
1671+
SUMMARY="${SUMMARY//,/, }"
1672+
echo "should_notify=true" >> $GITHUB_OUTPUT
1673+
echo "base_branch=$BASE_BRANCH" >> $GITHUB_OUTPUT
1674+
echo "head_ref=$HEAD_REF" >> $GITHUB_OUTPUT
1675+
echo "head_sha=$HEAD_SHA" >> $GITHUB_OUTPUT
1676+
echo "failed_summary=$SUMMARY" >> $GITHUB_OUTPUT
1677+
1678+
- name: Checkout kernel source
1679+
if: steps.decide.outputs.should_notify == 'true'
1680+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
1681+
with:
1682+
fetch-depth: 1
1683+
1684+
- name: Fetch notification script from main
1685+
if: steps.decide.outputs.should_notify == 'true'
1686+
run: |
1687+
git fetch origin main:main
1688+
git checkout origin/main -- .github/scripts/notify-slack-kernelci.sh
1689+
chmod +x .github/scripts/notify-slack-kernelci.sh
1690+
1691+
- name: Build Slack payload
1692+
if: steps.decide.outputs.should_notify == 'true'
1693+
run: |
1694+
.github/scripts/notify-slack-kernelci.sh \
1695+
--channel-id "${{ vars.SLACK_CHANNEL_LINUX_KERNEL }}" \
1696+
--base-branch "${{ steps.decide.outputs.base_branch }}" \
1697+
--head-ref "${{ steps.decide.outputs.head_ref }}" \
1698+
--head-sha "${{ steps.decide.outputs.head_sha }}" \
1699+
--pr-number "${{ needs.pre-setup.outputs.pr_number || '0' }}" \
1700+
--is-pr "${{ needs.pre-setup.outputs.is_pr || 'false' }}" \
1701+
--repo "${{ github.repository }}" \
1702+
--run-id "${{ github.run_id }}" \
1703+
--failed-stages "${{ steps.decide.outputs.failed_summary }}" \
1704+
--output slack_payload.json
1705+
1706+
- name: Post to Slack
1707+
if: steps.decide.outputs.should_notify == 'true'
1708+
uses: slackapi/slack-github-action@45a88b9581bfab2566dc881e2cd66d334e621e2c # v3.0.3
1709+
with:
1710+
method: chat.postMessage
1711+
token: ${{ secrets.GH_BOT_SLACK_TOKEN }}
1712+
payload-file-path: slack_payload.json

0 commit comments

Comments
 (0)