Push a commit and your Slack channel gets a message with 3-5 bullet points summarizing what changed and why. No bots to approve. Takes about 10 minutes to set up.
Before you start
Slack's Workflow Builder generates a webhook URL without needing admin approval for a custom app. This works on restricted workspaces.
Text, leave Data type as Text. Click Done. The webhook URL appears at the bottom — click Copy link and save it. Then click Save.Go to your repo → Settings → Secrets and variables → Actions → New repository secret. Add both of these.
| Secret name | Value |
|---|---|
| SLACK_WEBHOOK_URL | The webhook URL copied in step 1 |
| FIREWORKS_API_KEYconfigurable | Your API key from fireworks.ai → API Keys |
api.openai.com), Groq (api.groq.com), or a self-hosted model.
Create this file at .github/workflows/slack-notify.yml. Push it. The next commit after that triggers the first notification.
name: Slack Push Notification
on:
push:
branches: ['**']
permissions:
contents: read
jobs:
notify:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Summarize changes with Fireworks AI
id: ai
env:
FIREWORKS_API_KEY: ${{ secrets.FIREWORKS_API_KEY }}
BEFORE: ${{ github.event.before }}
AFTER: ${{ github.sha }}
REPO: ${{ github.repository }}
run: |
if git cat-file -e "$BEFORE" 2>/dev/null; then
RANGE="$BEFORE..$AFTER"
DIFF=$(git diff "$BEFORE" "$AFTER" | head -c 8000)
FILES=$(git diff --stat "$BEFORE" "$AFTER" | tail -40)
else
RANGE="$AFTER"
DIFF=$(git show "$AFTER" | head -c 8000)
FILES=$(git show --stat "$AFTER" | tail -40)
fi
COMMITS=$(git log --pretty=format:'• %s (%an)' "$RANGE" 2>/dev/null \
|| git log -1 --pretty=format:'• %s (%an)')
[ -n "$FILES" ] || FILES="n/a"
printf '%s' "$COMMITS" > "$RUNNER_TEMP/commits.txt"
clean_summary() {
printf '%s\n' "$1" \
| sed 's/\*\*//g; s/\*//g; s/`//g' \
| grep -E '^([•\-] |[0-9]+\. )' \
| sed 's/^[0-9]+\. /• /; s/^- /• /' \
| head -6
}
summarize() {
local prompt
prompt=$(printf 'Repo: %s\nCommits:\n%s\nFiles:\n%s\nDiff:\n%s' \
"$REPO" "$COMMITS" "$FILES" "$DIFF")
jq -n --arg p "$prompt" '{
model: "accounts/fireworks/models/kimi-k2p6",
max_tokens: 1000,
temperature: 0.1,
thinking: { type: "disabled" },
messages: [
{
role: "system",
content: "You write Slack push summaries. Output ONLY 3-5 bullet lines. Each line must start with •. Plain text only. No intro, no analysis, no headings, no markdown, no bold."
},
{ role: "user", content: $p }
]
}'
}
if [ -z "$FIREWORKS_API_KEY" ]; then
echo "FIREWORKS_API_KEY secret is not set"
SUMMARY="Summary unavailable — add FIREWORKS_API_KEY to repo secrets."
else
HTTP_CODE=$(curl -sS -o "$RUNNER_TEMP/fireworks.json" -w '%{http_code}' \
https://api.fireworks.ai/inference/v1/chat/completions \
-H "Authorization: Bearer $FIREWORKS_API_KEY" \
-H "Content-Type: application/json" \
--data "$(summarize)")
SUMMARY_RAW=$(jq -r '.choices[0].message.content // empty' "$RUNNER_TEMP/fireworks.json")
SUMMARY=$(clean_summary "$SUMMARY_RAW")
if [ -z "$SUMMARY" ] || [ "$HTTP_CODE" -ge 400 ]; then
echo "Fireworks call failed (HTTP $HTTP_CODE):" && cat "$RUNNER_TEMP/fireworks.json"
API_ERROR=$(jq -r '.error.message // empty' "$RUNNER_TEMP/fireworks.json")
if [ -n "$API_ERROR" ]; then
SUMMARY="• Summary unavailable — $API_ERROR"
elif [ -n "$SUMMARY_RAW" ]; then
echo "AI returned non-bullet output, raw response:" && printf '%s\n' "$SUMMARY_RAW"
SUMMARY="• Summary unavailable — model returned analysis instead of bullets."
else
SUMMARY="• Summary unavailable — AI returned empty response (HTTP $HTTP_CODE)."
fi
fi
fi
printf '%s' "$SUMMARY" > "$RUNNER_TEMP/summary.txt"
- name: Send to Slack
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
run: |
COMMITS=$(cat "$RUNNER_TEMP/commits.txt")
SUMMARY=$(cat "$RUNNER_TEMP/summary.txt")
[ -n "$COMMITS" ] || COMMITS="(no commits found)"
[ -n "$SUMMARY" ] || SUMMARY="(no summary)"
PAYLOAD=$(jq -n \
--arg repo "${{ github.repository }}" \
--arg branch "${{ github.ref_name }}" \
--arg actor "${{ github.actor }}" \
--arg compare "${{ github.event.compare }}" \
--arg commits "$COMMITS" \
--arg summary "$SUMMARY" \
'
def trunc($n):
if length > $n then .[0:$n] + "..." else . end;
{
text: (
"Push to " + ($repo | trunc(200)) + "\n"
+ "Branch: " + ($branch | trunc(200)) + " | By: " + ($actor | trunc(100)) + "\n\n"
+ "Commits\n" + ($commits | trunc(1500)) + "\n\n"
+ "Summary\n" + ($summary | trunc(1500)) + "\n\n"
+ $compare
)
}')
if [ -z "$SLACK_WEBHOOK_URL" ]; then
echo "SLACK_WEBHOOK_URL secret is not set"
exit 1
fi
HTTP_CODE=$(curl -sS -o "$RUNNER_TEMP/slack.json" -w '%{http_code}' -X POST \
-H 'Content-type: application/json' \
--data "$PAYLOAD" "$SLACK_WEBHOOK_URL")
SLACK_BODY=$(tr -d '\n' < "$RUNNER_TEMP/slack.json")
SLACK_OK=$(jq -r '.ok // empty' "$RUNNER_TEMP/slack.json" 2>/dev/null)
if [ "$HTTP_CODE" -lt 400 ] && { [ "$SLACK_BODY" = "ok" ] || [ "$SLACK_OK" = "true" ]; }; then
exit 0
fi
echo "Slack failed (HTTP $HTTP_CODE):" && cat "$RUNNER_TEMP/slack.json"
exit 1
What shows up in Slack
Any push to any branch runs the workflow. Full git history is checked out so it can diff against the previous commit.
The diff (capped at 8KB) and commit messages go to your configured model. Returns 3-5 plain bullet points. Falls back to raw commits if the call fails.
A plain text message posts to your channel via the Workflow Builder webhook. Includes branch, author, summary, and a diff link.
Workflow Builder webhooks only accept {"text": "..."}. They reject Block Kit JSON. Make sure you're using the workflow file above — an older version may have included a blocks payload.
The API key is wrong or the secret name doesn't match. Check that the GitHub secret name matches exactly what's in the workflow file, and that the key value is correct in your provider's dashboard.
You hit the free-tier rate limit. The workflow falls back to posting raw commit messages so Slack still gets a notification. Upgrade your plan or switch to a different provider if it happens often.
Confirm the file is at exactly .github/workflows/slack-notify.yml. Open the Actions tab in GitHub to see if a run was attempted. If nothing shows, the file path or YAML indentation is likely wrong.
The workflow bot isn't a member of the channel. Go to the channel and run /invite @YourWorkflowName. If you're unsure of the name, click the @ button in Slack and search for it first.