2929# - APP_SECRET_PEM_HUGGINGFACE_HUB_BOT : GitHub App private key for huggingface-hub-bot
3030# - APP_ID_HUB_SKILLS_REPO : GitHub App ID for creating tokens to access the skills repo
3131# - APP_SECRET_PREM_HUB_SKILLS_REPO : GitHub App private key for the skills repo
32- #
32+ # - SLACK_BOT_TOKEN : Slack Bot User OAuth Token (scopes: chat:write, reactions:write, files:write)
33+ #
34+ # Required repository variables:
35+ # - SLACK_CHANNEL_ID : Slack channel ID for release notifications
36+ #
3337# An additional secret for OpenCode authentication is needed for automated release notes generation
3438# (see the release-notes job below).
3539
7276 is_prerelease : ${{ steps.version.outputs.is_prerelease }}
7377 since_tag : ${{ steps.version.outputs.since_tag }}
7478 bump_type : ${{ steps.version.outputs.bump_type }}
79+ message_ts : ${{ steps.slack.outputs.message_ts }}
7580 steps :
7681 - uses : actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
7782 with :
@@ -187,6 +192,41 @@ jobs:
187192 echo "| Prerelease | \`$IS_PRERELEASE\` |" >> $GITHUB_STEP_SUMMARY
188193 echo "| Dry run | \`${{ inputs.dry_run }}\` |" >> $GITHUB_STEP_SUMMARY
189194
195+ - name : Notify Slack — release started
196+ if : ${{ !inputs.dry_run }}
197+ id : slack
198+ continue-on-error : true
199+ env :
200+ SLACK_BOT_TOKEN : ${{ secrets.SLACK_BOT_TOKEN }}
201+ SLACK_CHANNEL_ID : ${{ vars.SLACK_CHANNEL_ID }}
202+ run : |
203+ VERSION="${{ steps.version.outputs.version }}"
204+ TAG="${{ steps.version.outputs.tag }}"
205+ BRANCH="${{ steps.version.outputs.branch }}"
206+ RELEASE_TYPE="${{ inputs.release_type }}"
207+ RUN_URL="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
208+
209+ case "$RELEASE_TYPE" in
210+ minor-prerelease) TYPE_EMOJI="🏗️" TYPE_LABEL="Pre-release" ;;
211+ minor-release) TYPE_EMOJI="🚢" TYPE_LABEL="Release" ;;
212+ patch-release) TYPE_EMOJI="🐛" TYPE_LABEL="Patch" ;;
213+ esac
214+
215+ TEXT="${TYPE_EMOJI} *${TYPE_LABEL} \`${TAG}\` started*"$'\n'">Branch: \`${BRANCH}\` • <${RUN_URL}|View run>"
216+
217+ RESPONSE=$(curl -s -X POST https://slack.com/api/chat.postMessage \
218+ -H "Authorization: Bearer $SLACK_BOT_TOKEN" \
219+ -H "Content-Type: application/json" \
220+ -d "$(jq -n \
221+ --arg channel "$SLACK_CHANNEL_ID" \
222+ --arg text "$TEXT" \
223+ '{channel: $channel, text: $text, unfurl_links: false}')")
224+
225+ TS=$(echo "$RESPONSE" | jq -r '.ts // empty')
226+ if [[ -n "$TS" ]]; then
227+ echo "message_ts=$TS" >> $GITHUB_OUTPUT
228+ fi
229+
190230 - name : Create or checkout release branch
191231 run : |
192232 BRANCH="${{ steps.version.outputs.branch }}"
@@ -264,6 +304,27 @@ jobs:
264304 env :
265305 PYPI_TOKEN : ${{ secrets.PYPI_TOKEN_DIST_HUGGINGFACE_HUB }}
266306
307+ - name : Notify Slack
308+ if : ${{ always() && needs.prepare.outputs.message_ts }}
309+ continue-on-error : true
310+ env :
311+ SLACK_BOT_TOKEN : ${{ secrets.SLACK_BOT_TOKEN }}
312+ SLACK_CHANNEL_ID : ${{ vars.SLACK_CHANNEL_ID }}
313+ run : |
314+ if [[ "${{ job.status }}" == "success" ]]; then
315+ TEXT="✅ *PyPI* — \`huggingface-hub ${{ needs.prepare.outputs.version }}\` <https://pypi.org/project/huggingface-hub/#history|published>"
316+ else
317+ TEXT="❌ *PyPI* — \`huggingface-hub\` publish failed"
318+ fi
319+ curl -s -X POST https://slack.com/api/chat.postMessage \
320+ -H "Authorization: Bearer $SLACK_BOT_TOKEN" \
321+ -H "Content-Type: application/json" \
322+ -d "$(jq -n \
323+ --arg channel "$SLACK_CHANNEL_ID" \
324+ --arg text "$TEXT" \
325+ --arg thread_ts "${{ needs.prepare.outputs.message_ts }}" \
326+ '{channel: $channel, text: $text, thread_ts: $thread_ts, unfurl_links: false}')" > /dev/null
327+
267328 # ============================================================
268329 # 3. PUBLISH — PyPI (hf CLI)
269330 # ============================================================
@@ -289,6 +350,27 @@ jobs:
289350 env :
290351 PYPI_TOKEN : ${{ secrets.PYPI_TOKEN_DIST_HF }}
291352
353+ - name : Notify Slack
354+ if : ${{ always() && needs.prepare.outputs.message_ts }}
355+ continue-on-error : true
356+ env :
357+ SLACK_BOT_TOKEN : ${{ secrets.SLACK_BOT_TOKEN }}
358+ SLACK_CHANNEL_ID : ${{ vars.SLACK_CHANNEL_ID }}
359+ run : |
360+ if [[ "${{ job.status }}" == "success" ]]; then
361+ TEXT="✅ *PyPI* — \`hf (CLI) ${{ needs.prepare.outputs.version }}\` <https://pypi.org/project/hf/#history|published>"
362+ else
363+ TEXT="❌ *PyPI* — \`hf (CLI)\` publish failed"
364+ fi
365+ curl -s -X POST https://slack.com/api/chat.postMessage \
366+ -H "Authorization: Bearer $SLACK_BOT_TOKEN" \
367+ -H "Content-Type: application/json" \
368+ -d "$(jq -n \
369+ --arg channel "$SLACK_CHANNEL_ID" \
370+ --arg text "$TEXT" \
371+ --arg thread_ts "${{ needs.prepare.outputs.message_ts }}" \
372+ '{channel: $channel, text: $text, thread_ts: $thread_ts, unfurl_links: false}')" > /dev/null
373+
292374 # ============================================================
293375 # 4. RELEASE NOTES — manage GitHub releases
294376 #
@@ -430,6 +512,28 @@ jobs:
430512 --generate-notes
431513 fi
432514
515+ - name : Notify Slack
516+ if : ${{ always() && needs.prepare.outputs.message_ts }}
517+ continue-on-error : true
518+ env :
519+ SLACK_BOT_TOKEN : ${{ secrets.SLACK_BOT_TOKEN }}
520+ SLACK_CHANNEL_ID : ${{ vars.SLACK_CHANNEL_ID }}
521+ run : |
522+ RELEASES_URL="${{ github.server_url }}/${{ github.repository }}/releases"
523+ if [[ "${{ job.status }}" == "success" ]]; then
524+ TEXT="✅ *Release notes* — <${RELEASES_URL}|draft created> (\`${{ inputs.release_type }}\`)"
525+ else
526+ TEXT="❌ *Release notes* — failed"
527+ fi
528+ curl -s -X POST https://slack.com/api/chat.postMessage \
529+ -H "Authorization: Bearer $SLACK_BOT_TOKEN" \
530+ -H "Content-Type: application/json" \
531+ -d "$(jq -n \
532+ --arg channel "$SLACK_CHANNEL_ID" \
533+ --arg text "$TEXT" \
534+ --arg thread_ts "${{ needs.prepare.outputs.message_ts }}" \
535+ '{channel: $channel, text: $text, thread_ts: $thread_ts, unfurl_links: false}')" > /dev/null
536+
433537 # ============================================================
434538 # 4b. SLACK MESSAGE — generate Slack announcement for prereleases
435539 # ============================================================
@@ -502,6 +606,55 @@ jobs:
502606 cat ".release-notes/SLACK_MESSAGE_v${BASE_VERSION}.md" >> $GITHUB_STEP_SUMMARY
503607 echo '```' >> $GITHUB_STEP_SUMMARY
504608
609+ - name : Notify Slack
610+ if : ${{ always() && needs.prepare.outputs.message_ts }}
611+ continue-on-error : true
612+ env :
613+ SLACK_BOT_TOKEN : ${{ secrets.SLACK_BOT_TOKEN }}
614+ SLACK_CHANNEL_ID : ${{ vars.SLACK_CHANNEL_ID }}
615+ run : |
616+ VERSION="${{ needs.prepare.outputs.version }}"
617+ BASE_VERSION=$(echo "$VERSION" | sed -E 's/\.rc.*//')
618+ SLACK_MSG_FILE=".release-notes/SLACK_MESSAGE_v${BASE_VERSION}.md"
619+
620+ if [[ "${{ job.status }}" == "success" ]]; then
621+ TEXT="✅ *Slack announcement* — message generated"
622+ else
623+ TEXT="❌ *Slack announcement* — generation failed"
624+ fi
625+ curl -s -X POST https://slack.com/api/chat.postMessage \
626+ -H "Authorization: Bearer $SLACK_BOT_TOKEN" \
627+ -H "Content-Type: application/json" \
628+ -d "$(jq -n \
629+ --arg channel "$SLACK_CHANNEL_ID" \
630+ --arg text "$TEXT" \
631+ --arg thread_ts "${{ needs.prepare.outputs.message_ts }}" \
632+ '{channel: $channel, text: $text, thread_ts: $thread_ts, unfurl_links: false}')" > /dev/null
633+
634+ # Upload the generated Slack announcement as a file in the thread
635+ if [[ -f "$SLACK_MSG_FILE" ]]; then
636+ FILE_SIZE=$(wc -c < "$SLACK_MSG_FILE")
637+ UPLOAD_RESP=$(curl -s -X POST https://slack.com/api/files.getUploadURLExternal \
638+ -H "Authorization: Bearer $SLACK_BOT_TOKEN" \
639+ -H "Content-Type: application/x-www-form-urlencoded" \
640+ -d "filename=slack_announcement_v${BASE_VERSION}.md&length=${FILE_SIZE}")
641+
642+ UPLOAD_URL=$(echo "$UPLOAD_RESP" | jq -r '.upload_url // empty')
643+ FILE_ID=$(echo "$UPLOAD_RESP" | jq -r '.file_id // empty')
644+
645+ if [[ -n "$UPLOAD_URL" && -n "$FILE_ID" ]]; then
646+ curl -s -X POST "$UPLOAD_URL" -F "file=@${SLACK_MSG_FILE}" > /dev/null
647+ curl -s -X POST https://slack.com/api/files.completeUploadExternal \
648+ -H "Authorization: Bearer $SLACK_BOT_TOKEN" \
649+ -H "Content-Type: application/json" \
650+ -d "$(jq -n \
651+ --arg file_id "$FILE_ID" \
652+ --arg channel "$SLACK_CHANNEL_ID" \
653+ --arg thread_ts "${{ needs.prepare.outputs.message_ts }}" \
654+ '{files: [{id: $file_id}], channel_id: $channel, thread_ts: $thread_ts}')" > /dev/null
655+ fi
656+ fi
657+
505658 # ============================================================
506659 # 5. DOWNSTREAM TESTING — test RC in transformers, datasets, etc.
507660 # ============================================================
@@ -593,6 +746,31 @@ jobs:
593746 echo "- [Actions](https://github.com/huggingface/${{ matrix.target-repo }}/actions)" >> $GITHUB_STEP_SUMMARY
594747 echo "- [Compare](https://github.com/huggingface/${{ matrix.target-repo }}/compare/main...${BRANCH_NAME})" >> $GITHUB_STEP_SUMMARY
595748
749+ - name : Notify Slack
750+ if : ${{ always() && needs.prepare.outputs.message_ts }}
751+ continue-on-error : true
752+ env :
753+ SLACK_BOT_TOKEN : ${{ secrets.SLACK_BOT_TOKEN }}
754+ SLACK_CHANNEL_ID : ${{ vars.SLACK_CHANNEL_ID }}
755+ run : |
756+ REPO="${{ matrix.target-repo }}"
757+ VERSION="${{ needs.prepare.outputs.version }}"
758+ BRANCH_NAME="ci-test-huggingface-hub-${VERSION}-release"
759+ COMPARE_URL="https://github.com/huggingface/${REPO}/compare/main...${BRANCH_NAME}"
760+ if [[ "${{ job.status }}" == "success" ]]; then
761+ TEXT="✅ *Downstream* — \`${REPO}\` <${COMPARE_URL}|test branch pushed>"
762+ else
763+ TEXT="❌ *Downstream* — \`${REPO}\` failed"
764+ fi
765+ curl -s -X POST https://slack.com/api/chat.postMessage \
766+ -H "Authorization: Bearer $SLACK_BOT_TOKEN" \
767+ -H "Content-Type: application/json" \
768+ -d "$(jq -n \
769+ --arg channel "$SLACK_CHANNEL_ID" \
770+ --arg text "$TEXT" \
771+ --arg thread_ts "${{ needs.prepare.outputs.message_ts }}" \
772+ '{channel: $channel, text: $text, thread_ts: $thread_ts, unfurl_links: false}')" > /dev/null
773+
596774 # ============================================================
597775 # 6. POST-RELEASE — open PR to bump main to next dev version
598776 # ============================================================
@@ -621,6 +799,7 @@ jobs:
621799 git config user.email "huggingface-hub-bot[bot]@users.noreply.github.com"
622800
623801 - name : Create version bump PR
802+ id : bump_pr
624803 env :
625804 GH_TOKEN : ${{ steps.app_token.outputs.token }}
626805 run : |
@@ -646,10 +825,35 @@ jobs:
646825 git commit -m "Post-release: bump version to ${DEV_VERSION}"
647826 git push -u origin "$PR_BRANCH"
648827
649- gh pr create \
828+ PR_URL=$( gh pr create \
650829 --title "Post-release: bump version to ${DEV_VERSION}" \
651830 --body "Automated version bump after v${VERSION} release." \
652- --base main
831+ --base main)
832+ echo "pr_url=$PR_URL" >> $GITHUB_OUTPUT
833+
834+ - name : Notify Slack
835+ if : ${{ always() && needs.prepare.outputs.message_ts }}
836+ continue-on-error : true
837+ env :
838+ SLACK_BOT_TOKEN : ${{ secrets.SLACK_BOT_TOKEN }}
839+ SLACK_CHANNEL_ID : ${{ vars.SLACK_CHANNEL_ID }}
840+ run : |
841+ PR_URL="${{ steps.bump_pr.outputs.pr_url }}"
842+ if [[ "${{ job.status }}" == "success" && -n "$PR_URL" ]]; then
843+ TEXT="✅ *Post-release* — <${PR_URL}|version bump PR> opened"
844+ elif [[ "${{ job.status }}" == "success" ]]; then
845+ TEXT="✅ *Post-release* — skipped (version already bumped)"
846+ else
847+ TEXT="❌ *Post-release* — failed"
848+ fi
849+ curl -s -X POST https://slack.com/api/chat.postMessage \
850+ -H "Authorization: Bearer $SLACK_BOT_TOKEN" \
851+ -H "Content-Type: application/json" \
852+ -d "$(jq -n \
853+ --arg channel "$SLACK_CHANNEL_ID" \
854+ --arg text "$TEXT" \
855+ --arg thread_ts "${{ needs.prepare.outputs.message_ts }}" \
856+ '{channel: $channel, text: $text, thread_ts: $thread_ts, unfurl_links: false}')" > /dev/null
653857
654858 # ============================================================
655859 # 7. SYNC HF CLI SKILL — update skill docs in skills repo
@@ -724,3 +928,96 @@ jobs:
724928 labels : |
725929 automated
726930 cli-sync
931+
932+ - name : Notify Slack
933+ if : ${{ always() && needs.prepare.outputs.message_ts }}
934+ continue-on-error : true
935+ env :
936+ SLACK_BOT_TOKEN : ${{ secrets.SLACK_BOT_TOKEN }}
937+ SLACK_CHANNEL_ID : ${{ vars.SLACK_CHANNEL_ID }}
938+ run : |
939+ if [[ "${{ job.status }}" == "success" ]]; then
940+ TEXT="✅ *CLI skill sync* — done"
941+ else
942+ TEXT="❌ *CLI skill sync* — failed"
943+ fi
944+ curl -s -X POST https://slack.com/api/chat.postMessage \
945+ -H "Authorization: Bearer $SLACK_BOT_TOKEN" \
946+ -H "Content-Type: application/json" \
947+ -d "$(jq -n \
948+ --arg channel "$SLACK_CHANNEL_ID" \
949+ --arg text "$TEXT" \
950+ --arg thread_ts "${{ needs.prepare.outputs.message_ts }}" \
951+ '{channel: $channel, text: $text, thread_ts: $thread_ts, unfurl_links: false}')" > /dev/null
952+
953+ # ============================================================
954+ # 8. SLACK — final status update
955+ # ============================================================
956+ slack-complete :
957+ needs : [prepare, publish-pypi, publish-hf-cli, release-notes, slack-message, test-downstream, post-release, sync-hf-cli-skill]
958+ if : ${{ always() && !inputs.dry_run && needs.prepare.outputs.message_ts }}
959+ runs-on : ubuntu-latest
960+ steps :
961+ - name : Update Slack message with final status
962+ continue-on-error : true
963+ env :
964+ SLACK_BOT_TOKEN : ${{ secrets.SLACK_BOT_TOKEN }}
965+ SLACK_CHANNEL_ID : ${{ vars.SLACK_CHANNEL_ID }}
966+ run : |
967+ MESSAGE_TS="${{ needs.prepare.outputs.message_ts }}"
968+ TAG="${{ needs.prepare.outputs.tag }}"
969+ BRANCH="${{ needs.prepare.outputs.branch }}"
970+ RELEASE_TYPE="${{ inputs.release_type }}"
971+ RUN_URL="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
972+
973+ # Check if any job failed or was cancelled (including prepare itself,
974+ # which can fail after posting the Slack message, e.g. during git push)
975+ FAILED=false
976+ for result in \
977+ "${{ needs.prepare.result }}" \
978+ "${{ needs.publish-pypi.result }}" \
979+ "${{ needs.publish-hf-cli.result }}" \
980+ "${{ needs.release-notes.result }}" \
981+ "${{ needs.slack-message.result }}" \
982+ "${{ needs.test-downstream.result }}" \
983+ "${{ needs.post-release.result }}" \
984+ "${{ needs.sync-hf-cli-skill.result }}"; do
985+ if [[ "$result" == "failure" || "$result" == "cancelled" ]]; then
986+ FAILED=true
987+ break
988+ fi
989+ done
990+
991+ case "$RELEASE_TYPE" in
992+ minor-prerelease) TYPE_LABEL="Pre-release" ;;
993+ minor-release) TYPE_LABEL="Release" ;;
994+ patch-release) TYPE_LABEL="Patch" ;;
995+ esac
996+
997+ if [[ "$FAILED" == "true" ]]; then
998+ TEXT="❌ *${TYPE_LABEL} \`${TAG}\` failed*"$'\n'">Branch: \`${BRANCH}\` • <${RUN_URL}|View run>"
999+ REACTION="x"
1000+ else
1001+ TEXT="✅ *${TYPE_LABEL} \`${TAG}\` completed*"$'\n'">Branch: \`${BRANCH}\` • <${RUN_URL}|View run>"
1002+ REACTION="white_check_mark"
1003+ fi
1004+
1005+ # Update the original message
1006+ curl -s -X POST https://slack.com/api/chat.update \
1007+ -H "Authorization: Bearer $SLACK_BOT_TOKEN" \
1008+ -H "Content-Type: application/json" \
1009+ -d "$(jq -n \
1010+ --arg channel "$SLACK_CHANNEL_ID" \
1011+ --arg text "$TEXT" \
1012+ --arg ts "$MESSAGE_TS" \
1013+ '{channel: $channel, text: $text, ts: $ts}')" > /dev/null
1014+
1015+ # Add status reaction
1016+ curl -s -X POST https://slack.com/api/reactions.add \
1017+ -H "Authorization: Bearer $SLACK_BOT_TOKEN" \
1018+ -H "Content-Type: application/json" \
1019+ -d "$(jq -n \
1020+ --arg channel "$SLACK_CHANNEL_ID" \
1021+ --arg name "$REACTION" \
1022+ --arg timestamp "$MESSAGE_TS" \
1023+ '{channel: $channel, name: $name, timestamp: $timestamp}')" > /dev/null
0 commit comments