GitHub push notifications
with AI summaries

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

01
GitHub repo Any repo you can push to and add workflow files to.
02
Slack workspace with Workflow Builder Available on Pro plan and above. You'll see Automations in the sidebar if you have access.
03
AI provider API key The workflow uses Fireworks AI by default, but you can swap in any OpenAI-compatible provider.
01
Create a Slack webhook

Slack's Workflow Builder generates a webhook URL without needing admin approval for a custom app. This works on restricted workspaces.

1
In Slack, go to Automations in the sidebar. Click + New then select Build workflow.
2
You'll land on the workflow canvas. Click Choose an event under "Start the workflow..."
3
A panel opens. Under the Slack tab, click From a webhook — "Starts from a third-party event".
4
Click Set up variables. In the Key field type Text, leave Data type as Text. Click Done. The webhook URL appears at the bottom — click Copy link and save it. Then click Save.
5
Back on the canvas, click Add steps. From the Discover panel, click Send a message to a channel.
6
Select your channel from the dropdown. Click {} Insert a variable in the message body and select Text from the list. Click Save.
7
Click Finish up at the top right. Name your workflow, set permissions to your workspace, then click Publish.
Bot not posting? Go to your target channel and run /invite @YourWorkflowName. The workflow bot needs to be a channel member before it can post.
02
Add secrets to GitHub

Go to your repo → SettingsSecrets and variablesActionsNew 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
Using a different AI provider? The workflow calls any OpenAI-compatible endpoint. Change the secret name, endpoint URL, and model string in the workflow file to match your provider — e.g. OpenAI (api.openai.com), Groq (api.groq.com), or a self-hosted model.
Secret names are case-sensitive. A typo or extra space causes a silent failure — the workflow runs but nothing posts to Slack.
03
Add the workflow file

Create this file at .github/workflows/slack-notify.yml. Push it. The next commit after that triggers the first notification.

.github/workflows/slack-notify.yml
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

Push to team/myapp
Branch: main  |  By: raj
COMMITS
• fix token refresh on expired sessions (Raj)
• add retry logic for failed API calls (Raj)
SUMMARY
• Updated auth token refresh to handle expired sessions without forcing logout
• Added retry logic with exponential backoff for failed API requests
• Cleaned up unused imports in auth.py and api_client.py
https://github.com/team/myapp/compare/abc123...def456

How it works

Trigger

GitHub push

Any push to any branch runs the workflow. Full git history is checked out so it can diff against the previous commit.

Summarize

AI provider

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.

Notify

Slack message

A plain text message posts to your channel via the Workflow Builder webhook. Includes branch, author, summary, and a diff link.

Troubleshooting

invalid_blocks Slack activity log shows invalid_blocks

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.

HTTP 401 AI call failed with HTTP 401

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.

HTTP 429 AI call failed with HTTP 429

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.

no trigger Workflow doesn't run after a push

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.

bot blocked Workflow runs but nothing appears in Slack

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.