From dd054b9faed934b64600caf5c08a032cee165ab6 Mon Sep 17 00:00:00 2001 From: Goran Dodig Date: Mon, 26 Oct 2026 16:22:02 +0100 Subject: [PATCH 1/5] ci: post Slack notices with the bot token Post with the org SLACK_BOT_TOKEN to vars.SLACK_CHANNEL_ID instead of a per-repo webhook, and fail the step when Slack rejects a post; the release-start notice stays non-fatal. PR titles are escaped so they cannot mention @channel. E2E alerts keep their own webhook. --- .../workflows/nofity-slack-on-main-merge.yml | 22 --- .../workflows/notify-slack-on-main-merge.yml | 32 +++++ .github/workflows/publish-dev-to-pypi.yml | 43 +++--- .github/workflows/publish-install-script.yml | 39 ++++-- .github/workflows/publish-to-pypi.yml | 130 +++++++++++------- 5 files changed, 165 insertions(+), 101 deletions(-) delete mode 100644 .github/workflows/nofity-slack-on-main-merge.yml create mode 100644 .github/workflows/notify-slack-on-main-merge.yml diff --git a/.github/workflows/nofity-slack-on-main-merge.yml b/.github/workflows/nofity-slack-on-main-merge.yml deleted file mode 100644 index 1d5e8a05..00000000 --- a/.github/workflows/nofity-slack-on-main-merge.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: Notify Slack on Merge to Main - -on: - pull_request: - types: [closed] - branches: - - main - -jobs: - notify: - runs-on: ubuntu-latest - if: github.event.pull_request.merged == true - steps: - - name: Send notification to Slack - uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v4.0.0 - with: - webhook: ${{ secrets.SLACK_WEBHOOK_URL }} - webhook-type: incoming-webhook - payload: | - { - "text": ${{ toJSON(format('[Codeplain Client] PR <{0}|#{1}: {2}> was merged to main by `{3}`.', github.event.pull_request.html_url, github.event.pull_request.number, github.event.pull_request.title, github.actor)) }} - } diff --git a/.github/workflows/notify-slack-on-main-merge.yml b/.github/workflows/notify-slack-on-main-merge.yml new file mode 100644 index 00000000..3d7841c6 --- /dev/null +++ b/.github/workflows/notify-slack-on-main-merge.yml @@ -0,0 +1,32 @@ +name: Notify Slack on Merge to Main + +on: + pull_request: + types: [closed] + branches: + - main + +jobs: + notify: + runs-on: ubuntu-latest + if: github.event.pull_request.merged == true && vars.SLACK_CHANNEL_ID != '' + steps: + # Slack reads <, > and & as markup: escape them so a PR title cannot + # mention @channel or rewrite the link. + - name: Escape the PR title for Slack + id: pr + env: + TITLE: ${{ github.event.pull_request.title }} + run: | + printf 'title=%s\n' "$(printf '%s' "$TITLE" | sed -e 's/&/\&/g' -e 's//\>/g')" >> "$GITHUB_OUTPUT" + + - name: Send notification to Slack + uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v4.0.0 + with: + method: chat.postMessage + token: ${{ secrets.SLACK_BOT_TOKEN }} + errors: true + # toJSON quotes the title, so quotes and colons in it keep the payload valid. + payload: | + channel: ${{ vars.SLACK_CHANNEL_ID }} + text: ${{ toJSON(format('[Codeplain Client] PR <{0}|#{1}: {2}> was merged to main by `{3}`.', github.event.pull_request.html_url, github.event.pull_request.number, steps.pr.outputs.title, github.actor)) }} diff --git a/.github/workflows/publish-dev-to-pypi.yml b/.github/workflows/publish-dev-to-pypi.yml index 64f58372..809f3c83 100644 --- a/.github/workflows/publish-dev-to-pypi.yml +++ b/.github/workflows/publish-dev-to-pypi.yml @@ -110,9 +110,6 @@ jobs: # authenticate this run — no long-lived API token needed. id-token: write - env: - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} - steps: - name: Download distribution packages uses: actions/download-artifact@v8 @@ -127,21 +124,31 @@ jobs: # version should be a no-op, not a red build. skip-existing: true + # Spell out the --pre install: a plain "pip install codeplain" will + # NOT pick this up, which otherwise reads as a broken publish. - name: Notify Slack (success) - if: ${{ success() && env.SLACK_WEBHOOK_URL != '' }} - env: - VERSION: ${{ needs.build.outputs.version }} - run: | - # Spell out the --pre install: a plain "pip install codeplain" will - # NOT pick this up, which otherwise reads as a broken publish. - TEXT="*Codeplain Client*: dev build \`$VERSION\` published to PyPI :test_tube:. Normal installs are unaffected — try it with \`pip install --pre codeplain==$VERSION\`. Pre-release builds talk to \`api.test.codeplain.ai\` by default." - curl -s -X POST "$SLACK_WEBHOOK_URL" \ - -H 'Content-type: application/json' \ - --data "$(jq -n --arg t "$TEXT" '{text:$t}')" + if: ${{ success() && vars.SLACK_CHANNEL_ID != '' }} + uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v4.0.0 + with: + method: chat.postMessage + token: ${{ secrets.SLACK_BOT_TOKEN }} + errors: true + payload: | + channel: ${{ vars.SLACK_CHANNEL_ID }} + text: >- + *Codeplain Client*: dev build `${{ needs.build.outputs.version }}` published to PyPI :test_tube:. + Normal installs are unaffected — try it with `pip install --pre codeplain==${{ needs.build.outputs.version }}`. + Pre-release builds talk to `api.test.codeplain.ai` by default. - name: Notify Slack (failure) - if: ${{ failure() && env.SLACK_WEBHOOK_URL != '' }} - run: | - curl -s -X POST "$SLACK_WEBHOOK_URL" \ - -H 'Content-type: application/json' \ - --data "{\"text\":\"*Codeplain Client*: Publishing dev build to PyPI FAILED :x: (commit \`${{ github.sha }}\`). See <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|the run log>.\"}" + if: ${{ failure() && vars.SLACK_CHANNEL_ID != '' }} + uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v4.0.0 + with: + method: chat.postMessage + token: ${{ secrets.SLACK_BOT_TOKEN }} + errors: true + payload: | + channel: ${{ vars.SLACK_CHANNEL_ID }} + text: >- + *Codeplain Client*: Publishing dev build to PyPI FAILED :x: (commit `${{ github.sha }}`). + See <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|the run log>. diff --git a/.github/workflows/publish-install-script.yml b/.github/workflows/publish-install-script.yml index bc0cf251..2a603612 100644 --- a/.github/workflows/publish-install-script.yml +++ b/.github/workflows/publish-install-script.yml @@ -14,10 +14,10 @@ jobs: name: Upload to Cloudflare R2 runs-on: ubuntu-latest - env: - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} - steps: + - name: Short SHA for Slack + run: echo "SHORT_SHA=${GITHUB_SHA:0:7}" >> "$GITHUB_ENV" + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Upload install scripts to R2 @@ -58,16 +58,29 @@ jobs: echo "✓ examples.ps1 uploaded to R2" - name: Notify Slack (success) - if: ${{ success() && env.SLACK_WEBHOOK_URL != '' }} - run: | - curl -s -X POST "$SLACK_WEBHOOK_URL" \ - -H 'Content-type: application/json' \ - --data "{\"text\":\"*Codeplain Client*: Install scripts deployed to R2 :white_check_mark: (\`${GITHUB_SHA:0:7}\`, by *${{ github.actor }}*).\"}" + if: ${{ success() && vars.SLACK_CHANNEL_ID != '' }} + uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v4.0.0 + with: + method: chat.postMessage + token: ${{ secrets.SLACK_BOT_TOKEN }} + errors: true + payload: | + channel: ${{ vars.SLACK_CHANNEL_ID }} + text: >- + *Codeplain Client*: Install scripts deployed to R2 :white_check_mark: + (`${{ env.SHORT_SHA }}`, by *${{ github.actor }}*). - name: Notify Slack (failure) - if: ${{ failure() && env.SLACK_WEBHOOK_URL != '' }} - run: | - curl -s -X POST "$SLACK_WEBHOOK_URL" \ - -H 'Content-type: application/json' \ - --data "{\"text\":\"*Codeplain Client*: Install script deploy to R2 FAILED :x: (\`${GITHUB_SHA:0:7}\`). Users may be getting a stale installer. See <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|the run log>.\"}" + if: ${{ failure() && vars.SLACK_CHANNEL_ID != '' }} + uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v4.0.0 + with: + method: chat.postMessage + token: ${{ secrets.SLACK_BOT_TOKEN }} + errors: true + payload: | + channel: ${{ vars.SLACK_CHANNEL_ID }} + text: >- + *Codeplain Client*: Install script deploy to R2 FAILED :x: (`${{ env.SHORT_SHA }}`). + Users may be getting a stale installer. + See <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|the run log>. diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index 11db4a24..cb8b139d 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -15,21 +15,26 @@ jobs: permissions: contents: write - env: - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} - steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: # hatch-vcs derives the version from git tags, so we need full history. fetch-depth: 0 + # Non-fatal: Slack being down must not block the release. - name: Notify Slack (start) - if: ${{ env.SLACK_WEBHOOK_URL != '' }} - run: | - curl -s -X POST "$SLACK_WEBHOOK_URL" \ - -H 'Content-type: application/json' \ - --data "{\"text\":\"*Codeplain Client*: Release \`${{ github.ref_name }}\` published by *${{ github.actor }}* — building and publishing to PyPI...\"}" + if: ${{ vars.SLACK_CHANNEL_ID != '' }} + continue-on-error: true + uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v4.0.0 + with: + method: chat.postMessage + token: ${{ secrets.SLACK_BOT_TOKEN }} + errors: true + payload: | + channel: ${{ vars.SLACK_CHANNEL_ID }} + text: >- + *Codeplain Client*: Release `${{ github.ref_name }}` published by *${{ github.actor }}* + — building and publishing to PyPI... - name: Install uv # uv provisions its own Python (honoring requires-python), so no @@ -58,11 +63,17 @@ jobs: gh release upload ${{ github.ref_name }} dist/* --clobber - name: Notify Slack (build failure) - if: ${{ failure() && env.SLACK_WEBHOOK_URL != '' }} - run: | - curl -s -X POST "$SLACK_WEBHOOK_URL" \ - -H 'Content-type: application/json' \ - --data "{\"text\":\"*Codeplain Client*: Build of \`${{ github.ref_name }}\` FAILED :x:. Nothing was published to PyPI. See <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|the run log>.\"}" + if: ${{ failure() && vars.SLACK_CHANNEL_ID != '' }} + uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v4.0.0 + with: + method: chat.postMessage + token: ${{ secrets.SLACK_BOT_TOKEN }} + errors: true + payload: | + channel: ${{ vars.SLACK_CHANNEL_ID }} + text: >- + *Codeplain Client*: Build of `${{ github.ref_name }}` FAILED :x:. Nothing was published to PyPI. + See <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|the run log>. publish-to-pypi: name: Publish to PyPI @@ -76,10 +87,14 @@ jobs: # authenticate this run — no long-lived API token needed. id-token: write - env: - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} - steps: + # The tag (v0.4.0rc1) without its "v", for the install command in Slack. + - name: Version from tag + id: version + env: + TAG: ${{ github.ref_name }} + run: echo "version=${TAG#v}" >> "$GITHUB_OUTPUT" + - name: Download distribution packages uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: @@ -89,32 +104,48 @@ jobs: - name: Publish to PyPI uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # release/v1 - - name: Notify Slack (success) - if: ${{ success() && env.SLACK_WEBHOOK_URL != '' }} - env: - # GitHub marks the release itself as a pre-release; the tag - # (v0.4.0rc1) carries the same intent for PEP 440. - IS_PRERELEASE: ${{ github.event.release.prerelease }} - TAG: ${{ github.ref_name }} - run: | - VERSION="${TAG#v}" - if [ "$IS_PRERELEASE" = "true" ]; then - # pip and uv skip pre-releases by default, so "--upgrade" would - # NOT pick this up. Tell people the command that actually works. - TEXT="*Codeplain Client*: pre-release \`$TAG\` published to PyPI :test_tube:. Normal installs are unaffected — try it with \`pip install --pre codeplain==$VERSION\`." - else - TEXT="*Codeplain Client*: \`$TAG\` published to PyPI :white_check_mark:. Install with \`pip install --upgrade codeplain\`." - fi - curl -s -X POST "$SLACK_WEBHOOK_URL" \ - -H 'Content-type: application/json' \ - --data "$(jq -n --arg t "$TEXT" '{text:$t}')" + # GitHub marks the release itself as a pre-release; the tag (v0.4.0rc1) + # carries the same intent for PEP 440. pip and uv skip pre-releases by + # default, so "--upgrade" would NOT pick one up. Tell people the command + # that actually works. + - name: Notify Slack (pre-release published) + if: ${{ success() && github.event.release.prerelease && vars.SLACK_CHANNEL_ID != '' }} + uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v4.0.0 + with: + method: chat.postMessage + token: ${{ secrets.SLACK_BOT_TOKEN }} + errors: true + payload: | + channel: ${{ vars.SLACK_CHANNEL_ID }} + text: >- + *Codeplain Client*: pre-release `${{ github.ref_name }}` published to PyPI :test_tube:. + Normal installs are unaffected — try it with `pip install --pre codeplain==${{ steps.version.outputs.version }}`. + + - name: Notify Slack (release published) + if: ${{ success() && !github.event.release.prerelease && vars.SLACK_CHANNEL_ID != '' }} + uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v4.0.0 + with: + method: chat.postMessage + token: ${{ secrets.SLACK_BOT_TOKEN }} + errors: true + payload: | + channel: ${{ vars.SLACK_CHANNEL_ID }} + text: >- + *Codeplain Client*: `${{ github.ref_name }}` published to PyPI :white_check_mark:. + Install with `pip install --upgrade codeplain`. - name: Notify Slack (failure) - if: ${{ failure() && env.SLACK_WEBHOOK_URL != '' }} - run: | - curl -s -X POST "$SLACK_WEBHOOK_URL" \ - -H 'Content-type: application/json' \ - --data "{\"text\":\"*Codeplain Client*: Publishing \`${{ github.ref_name }}\` to PyPI FAILED :x:. See <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|the run log>.\"}" + if: ${{ failure() && vars.SLACK_CHANNEL_ID != '' }} + uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v4.0.0 + with: + method: chat.postMessage + token: ${{ secrets.SLACK_BOT_TOKEN }} + errors: true + payload: | + channel: ${{ vars.SLACK_CHANNEL_ID }} + text: >- + *Codeplain Client*: Publishing `${{ github.ref_name }}` to PyPI FAILED :x:. + See <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|the run log>. # Release pipelines to be turned on only after the rest of the infrastructure # is ready. @@ -129,9 +160,6 @@ jobs: # permissions: # contents: read # - # env: - # SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} - # # steps: # - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 # with: @@ -152,8 +180,14 @@ jobs: # # continue-on-error keeps the job green, so this checks the step's own # # outcome — `failure()` would never fire. # - name: Notify Slack (release not recorded) - # if: ${{ steps.linear.outcome == 'failure' && env.SLACK_WEBHOOK_URL != '' }} - # run: | - # curl -s -X POST "$SLACK_WEBHOOK_URL" \ - # -H 'Content-type: application/json' \ - # --data "{\"text\":\"*Codeplain Client*: \`${{ github.ref_name }}\` is on PyPI, but recording it in Linear FAILED :warning:. See <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|the run log>.\"}" + # if: ${{ steps.linear.outcome == 'failure' && vars.SLACK_CHANNEL_ID != '' }} + # uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v4.0.0 + # with: + # method: chat.postMessage + # token: ${{ secrets.SLACK_BOT_TOKEN }} + # errors: true + # payload: | + # channel: ${{ vars.SLACK_CHANNEL_ID }} + # text: >- + # *Codeplain Client*: `${{ github.ref_name }}` is on PyPI, but recording it in Linear FAILED :warning:. + # See <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|the run log>. From 2a441c81c3826e01e67efa4967707c2ead3527ed Mon Sep 17 00:00:00 2001 From: Goran Dodig Date: Mon, 26 Oct 2026 21:12:01 +0100 Subject: [PATCH 2/5] ci: fix Slack notice order and fork merges Post the failure notice before the success notice, so a failed success post cannot report a good run as failed. Run the merge notice on pull_request_target with no token permissions, so merged fork PRs post too, and strip line breaks from the title. --- .../workflows/notify-slack-on-main-merge.yml | 9 +++++-- .github/workflows/publish-dev-to-pypi.yml | 23 ++++++++-------- .github/workflows/publish-install-script.yml | 19 ++++++------- .github/workflows/publish-to-pypi.yml | 27 ++++++++++--------- 4 files changed, 43 insertions(+), 35 deletions(-) diff --git a/.github/workflows/notify-slack-on-main-merge.yml b/.github/workflows/notify-slack-on-main-merge.yml index 3d7841c6..0f6fff24 100644 --- a/.github/workflows/notify-slack-on-main-merge.yml +++ b/.github/workflows/notify-slack-on-main-merge.yml @@ -1,11 +1,16 @@ name: Notify Slack on Merge to Main +# pull_request_target runs this file from main with secrets, so merged PRs from +# forks post too. Nothing from the PR is checked out or run; the title only +# reaches the shell through an env var. on: - pull_request: + pull_request_target: types: [closed] branches: - main +permissions: {} + jobs: notify: runs-on: ubuntu-latest @@ -18,7 +23,7 @@ jobs: env: TITLE: ${{ github.event.pull_request.title }} run: | - printf 'title=%s\n' "$(printf '%s' "$TITLE" | sed -e 's/&/\&/g' -e 's//\>/g')" >> "$GITHUB_OUTPUT" + printf 'title=%s\n' "$(printf '%s' "$TITLE" | tr '\r\n' ' ' | sed -e 's/&/\&/g' -e 's//\>/g')" >> "$GITHUB_OUTPUT" - name: Send notification to Slack uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v4.0.0 diff --git a/.github/workflows/publish-dev-to-pypi.yml b/.github/workflows/publish-dev-to-pypi.yml index 809f3c83..6ba3ffaa 100644 --- a/.github/workflows/publish-dev-to-pypi.yml +++ b/.github/workflows/publish-dev-to-pypi.yml @@ -124,10 +124,9 @@ jobs: # version should be a no-op, not a red build. skip-existing: true - # Spell out the --pre install: a plain "pip install codeplain" will - # NOT pick this up, which otherwise reads as a broken publish. - - name: Notify Slack (success) - if: ${{ success() && vars.SLACK_CHANNEL_ID != '' }} + # Before the success notice, so a failed success post cannot trigger it. + - name: Notify Slack (failure) + if: ${{ failure() && vars.SLACK_CHANNEL_ID != '' }} uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v4.0.0 with: method: chat.postMessage @@ -136,12 +135,13 @@ jobs: payload: | channel: ${{ vars.SLACK_CHANNEL_ID }} text: >- - *Codeplain Client*: dev build `${{ needs.build.outputs.version }}` published to PyPI :test_tube:. - Normal installs are unaffected — try it with `pip install --pre codeplain==${{ needs.build.outputs.version }}`. - Pre-release builds talk to `api.test.codeplain.ai` by default. + *Codeplain Client*: Publishing dev build to PyPI FAILED :x: (commit `${{ github.sha }}`). + See <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|the run log>. - - name: Notify Slack (failure) - if: ${{ failure() && vars.SLACK_CHANNEL_ID != '' }} + # Spell out the --pre install: a plain "pip install codeplain" will + # NOT pick this up, which otherwise reads as a broken publish. + - name: Notify Slack (success) + if: ${{ success() && vars.SLACK_CHANNEL_ID != '' }} uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v4.0.0 with: method: chat.postMessage @@ -150,5 +150,6 @@ jobs: payload: | channel: ${{ vars.SLACK_CHANNEL_ID }} text: >- - *Codeplain Client*: Publishing dev build to PyPI FAILED :x: (commit `${{ github.sha }}`). - See <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|the run log>. + *Codeplain Client*: dev build `${{ needs.build.outputs.version }}` published to PyPI :test_tube:. + Normal installs are unaffected — try it with `pip install --pre codeplain==${{ needs.build.outputs.version }}`. + Pre-release builds talk to `api.test.codeplain.ai` by default. diff --git a/.github/workflows/publish-install-script.yml b/.github/workflows/publish-install-script.yml index 2a603612..9d5e5f82 100644 --- a/.github/workflows/publish-install-script.yml +++ b/.github/workflows/publish-install-script.yml @@ -57,8 +57,9 @@ jobs: --content-type "text/plain" echo "✓ examples.ps1 uploaded to R2" - - name: Notify Slack (success) - if: ${{ success() && vars.SLACK_CHANNEL_ID != '' }} + # Before the success notice, so a failed success post cannot trigger it. + - name: Notify Slack (failure) + if: ${{ failure() && vars.SLACK_CHANNEL_ID != '' }} uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v4.0.0 with: method: chat.postMessage @@ -67,11 +68,12 @@ jobs: payload: | channel: ${{ vars.SLACK_CHANNEL_ID }} text: >- - *Codeplain Client*: Install scripts deployed to R2 :white_check_mark: - (`${{ env.SHORT_SHA }}`, by *${{ github.actor }}*). + *Codeplain Client*: Install script deploy to R2 FAILED :x: (`${{ env.SHORT_SHA }}`). + Users may be getting a stale installer. + See <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|the run log>. - - name: Notify Slack (failure) - if: ${{ failure() && vars.SLACK_CHANNEL_ID != '' }} + - name: Notify Slack (success) + if: ${{ success() && vars.SLACK_CHANNEL_ID != '' }} uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v4.0.0 with: method: chat.postMessage @@ -80,7 +82,6 @@ jobs: payload: | channel: ${{ vars.SLACK_CHANNEL_ID }} text: >- - *Codeplain Client*: Install script deploy to R2 FAILED :x: (`${{ env.SHORT_SHA }}`). - Users may be getting a stale installer. - See <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|the run log>. + *Codeplain Client*: Install scripts deployed to R2 :white_check_mark: + (`${{ env.SHORT_SHA }}`, by *${{ github.actor }}*). diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index cb8b139d..e8e219b2 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -104,6 +104,20 @@ jobs: - name: Publish to PyPI uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # release/v1 + # Before the success notice, so a failed success post cannot trigger it. + - name: Notify Slack (failure) + if: ${{ failure() && vars.SLACK_CHANNEL_ID != '' }} + uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v4.0.0 + with: + method: chat.postMessage + token: ${{ secrets.SLACK_BOT_TOKEN }} + errors: true + payload: | + channel: ${{ vars.SLACK_CHANNEL_ID }} + text: >- + *Codeplain Client*: Publishing `${{ github.ref_name }}` to PyPI FAILED :x:. + See <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|the run log>. + # GitHub marks the release itself as a pre-release; the tag (v0.4.0rc1) # carries the same intent for PEP 440. pip and uv skip pre-releases by # default, so "--upgrade" would NOT pick one up. Tell people the command @@ -134,19 +148,6 @@ jobs: *Codeplain Client*: `${{ github.ref_name }}` published to PyPI :white_check_mark:. Install with `pip install --upgrade codeplain`. - - name: Notify Slack (failure) - if: ${{ failure() && vars.SLACK_CHANNEL_ID != '' }} - uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v4.0.0 - with: - method: chat.postMessage - token: ${{ secrets.SLACK_BOT_TOKEN }} - errors: true - payload: | - channel: ${{ vars.SLACK_CHANNEL_ID }} - text: >- - *Codeplain Client*: Publishing `${{ github.ref_name }}` to PyPI FAILED :x:. - See <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|the run log>. - # Release pipelines to be turned on only after the rest of the infrastructure # is ready. # From e14afbe4a0dddda0f76cf5f1882bfa57a5b1714f Mon Sep 17 00:00:00 2001 From: Goran Dodig Date: Mon, 26 Oct 2026 21:27:00 +0100 Subject: [PATCH 3/5] ci: post end-of-run Slack notices from their own job A failed Slack post now fails only the notify job, not the deploy or publish job or its GitHub deployment, and "Re-run failed jobs" re-posts without running the work again. Start notices stay in the work jobs. --- .github/workflows/publish-dev-to-pypi.yml | 15 ++++- .github/workflows/publish-install-script.yml | 21 +++++-- .github/workflows/publish-to-pypi.yml | 58 ++++++++++++-------- 3 files changed, 61 insertions(+), 33 deletions(-) diff --git a/.github/workflows/publish-dev-to-pypi.yml b/.github/workflows/publish-dev-to-pypi.yml index 6ba3ffaa..4a81f367 100644 --- a/.github/workflows/publish-dev-to-pypi.yml +++ b/.github/workflows/publish-dev-to-pypi.yml @@ -124,9 +124,18 @@ jobs: # version should be a no-op, not a red build. skip-existing: true - # Before the success notice, so a failed success post cannot trigger it. + # End-of-run notices run in their own job: a failed Slack post fails only this + # job, not the publish or its GitHub deployment, and "Re-run failed jobs" + # re-posts without publishing again. + notify: + name: Notify Slack + needs: [build, publish-dev] + if: ${{ !cancelled() && vars.SLACK_CHANNEL_ID != '' && contains(fromJSON('["success", "failure"]'), needs.publish-dev.result) }} + runs-on: ubuntu-latest + + steps: - name: Notify Slack (failure) - if: ${{ failure() && vars.SLACK_CHANNEL_ID != '' }} + if: ${{ needs.publish-dev.result == 'failure' }} uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v4.0.0 with: method: chat.postMessage @@ -141,7 +150,7 @@ jobs: # Spell out the --pre install: a plain "pip install codeplain" will # NOT pick this up, which otherwise reads as a broken publish. - name: Notify Slack (success) - if: ${{ success() && vars.SLACK_CHANNEL_ID != '' }} + if: ${{ needs.publish-dev.result == 'success' }} uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v4.0.0 with: method: chat.postMessage diff --git a/.github/workflows/publish-install-script.yml b/.github/workflows/publish-install-script.yml index 9d5e5f82..d291bc7f 100644 --- a/.github/workflows/publish-install-script.yml +++ b/.github/workflows/publish-install-script.yml @@ -15,9 +15,6 @@ jobs: runs-on: ubuntu-latest steps: - - name: Short SHA for Slack - run: echo "SHORT_SHA=${GITHUB_SHA:0:7}" >> "$GITHUB_ENV" - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Upload install scripts to R2 @@ -57,9 +54,21 @@ jobs: --content-type "text/plain" echo "✓ examples.ps1 uploaded to R2" - # Before the success notice, so a failed success post cannot trigger it. + # End-of-run notices run in their own job: a failed Slack post fails only this + # job, and "Re-run failed jobs" re-posts without uploading again. + notify: + name: Notify Slack + needs: publish + if: ${{ !cancelled() && vars.SLACK_CHANNEL_ID != '' }} + runs-on: ubuntu-latest + permissions: {} + + steps: + - name: Short SHA + run: echo "SHORT_SHA=${GITHUB_SHA:0:7}" >> "$GITHUB_ENV" + - name: Notify Slack (failure) - if: ${{ failure() && vars.SLACK_CHANNEL_ID != '' }} + if: ${{ needs.publish.result == 'failure' }} uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v4.0.0 with: method: chat.postMessage @@ -73,7 +82,7 @@ jobs: See <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|the run log>. - name: Notify Slack (success) - if: ${{ success() && vars.SLACK_CHANNEL_ID != '' }} + if: ${{ needs.publish.result == 'success' }} uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v4.0.0 with: method: chat.postMessage diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index e8e219b2..7afd767d 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -62,19 +62,6 @@ jobs: run: | gh release upload ${{ github.ref_name }} dist/* --clobber - - name: Notify Slack (build failure) - if: ${{ failure() && vars.SLACK_CHANNEL_ID != '' }} - uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v4.0.0 - with: - method: chat.postMessage - token: ${{ secrets.SLACK_BOT_TOKEN }} - errors: true - payload: | - channel: ${{ vars.SLACK_CHANNEL_ID }} - text: >- - *Codeplain Client*: Build of `${{ github.ref_name }}` FAILED :x:. Nothing was published to PyPI. - See <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|the run log>. - publish-to-pypi: name: Publish to PyPI needs: build @@ -88,13 +75,6 @@ jobs: id-token: write steps: - # The tag (v0.4.0rc1) without its "v", for the install command in Slack. - - name: Version from tag - id: version - env: - TAG: ${{ github.ref_name }} - run: echo "version=${TAG#v}" >> "$GITHUB_OUTPUT" - - name: Download distribution packages uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: @@ -104,9 +84,31 @@ jobs: - name: Publish to PyPI uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # release/v1 - # Before the success notice, so a failed success post cannot trigger it. + # End-of-run notices run in their own job: a failed Slack post fails only this + # job, not the publish or its GitHub deployment, and "Re-run failed jobs" + # re-posts without publishing again. + notify: + name: Notify Slack + needs: [build, publish-to-pypi] + if: ${{ !cancelled() && vars.SLACK_CHANNEL_ID != '' }} + runs-on: ubuntu-latest + + steps: + - name: Notify Slack (build failure) + if: ${{ needs.build.result == 'failure' }} + uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v4.0.0 + with: + method: chat.postMessage + token: ${{ secrets.SLACK_BOT_TOKEN }} + errors: true + payload: | + channel: ${{ vars.SLACK_CHANNEL_ID }} + text: >- + *Codeplain Client*: Build of `${{ github.ref_name }}` FAILED :x:. Nothing was published to PyPI. + See <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|the run log>. + - name: Notify Slack (failure) - if: ${{ failure() && vars.SLACK_CHANNEL_ID != '' }} + if: ${{ needs.publish-to-pypi.result == 'failure' }} uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v4.0.0 with: method: chat.postMessage @@ -118,12 +120,20 @@ jobs: *Codeplain Client*: Publishing `${{ github.ref_name }}` to PyPI FAILED :x:. See <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|the run log>. + # The tag (v0.4.0rc1) without its "v", for the install command below. + - name: Version from tag + id: version + if: ${{ needs.publish-to-pypi.result == 'success' }} + env: + TAG: ${{ github.ref_name }} + run: echo "version=${TAG#v}" >> "$GITHUB_OUTPUT" + # GitHub marks the release itself as a pre-release; the tag (v0.4.0rc1) # carries the same intent for PEP 440. pip and uv skip pre-releases by # default, so "--upgrade" would NOT pick one up. Tell people the command # that actually works. - name: Notify Slack (pre-release published) - if: ${{ success() && github.event.release.prerelease && vars.SLACK_CHANNEL_ID != '' }} + if: ${{ needs.publish-to-pypi.result == 'success' && github.event.release.prerelease }} uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v4.0.0 with: method: chat.postMessage @@ -136,7 +146,7 @@ jobs: Normal installs are unaffected — try it with `pip install --pre codeplain==${{ steps.version.outputs.version }}`. - name: Notify Slack (release published) - if: ${{ success() && !github.event.release.prerelease && vars.SLACK_CHANNEL_ID != '' }} + if: ${{ needs.publish-to-pypi.result == 'success' && !github.event.release.prerelease }} uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v4.0.0 with: method: chat.postMessage From c47a9773c2c669ce22a20616fcea49f904348576 Mon Sep 17 00:00:00 2001 From: Goran Dodig Date: Mon, 26 Oct 2026 21:54:26 +0100 Subject: [PATCH 4/5] ci: post to Slack when a dev build fails to build A failed build skipped the publish job, so the notify job found nothing to report. --- .github/workflows/publish-dev-to-pypi.yml | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish-dev-to-pypi.yml b/.github/workflows/publish-dev-to-pypi.yml index 4a81f367..2976a02c 100644 --- a/.github/workflows/publish-dev-to-pypi.yml +++ b/.github/workflows/publish-dev-to-pypi.yml @@ -130,10 +130,23 @@ jobs: notify: name: Notify Slack needs: [build, publish-dev] - if: ${{ !cancelled() && vars.SLACK_CHANNEL_ID != '' && contains(fromJSON('["success", "failure"]'), needs.publish-dev.result) }} + if: ${{ !cancelled() && vars.SLACK_CHANNEL_ID != '' }} runs-on: ubuntu-latest steps: + - name: Notify Slack (build failure) + if: ${{ needs.build.result == 'failure' }} + uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v4.0.0 + with: + method: chat.postMessage + token: ${{ secrets.SLACK_BOT_TOKEN }} + errors: true + payload: | + channel: ${{ vars.SLACK_CHANNEL_ID }} + text: >- + *Codeplain Client*: Building the dev build FAILED :x: (commit `${{ github.sha }}`). Nothing was published to PyPI. + See <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|the run log>. + - name: Notify Slack (failure) if: ${{ needs.publish-dev.result == 'failure' }} uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v4.0.0 From 9fe1bc7b36d6cae668ce617f2d9c3acbb38f69a5 Mon Sep 17 00:00:00 2001 From: Goran Dodig Date: Tue, 27 Oct 2026 14:06:58 +0100 Subject: [PATCH 5/5] ci: use the short SHA in the dev build failure notices Matches publish-install-script.yml; these printed the full 40-char SHA. --- .github/workflows/publish-dev-to-pypi.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish-dev-to-pypi.yml b/.github/workflows/publish-dev-to-pypi.yml index 2976a02c..b954f2bc 100644 --- a/.github/workflows/publish-dev-to-pypi.yml +++ b/.github/workflows/publish-dev-to-pypi.yml @@ -134,6 +134,9 @@ jobs: runs-on: ubuntu-latest steps: + - name: Short SHA + run: echo "SHORT_SHA=${GITHUB_SHA:0:7}" >> "$GITHUB_ENV" + - name: Notify Slack (build failure) if: ${{ needs.build.result == 'failure' }} uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v4.0.0 @@ -144,7 +147,7 @@ jobs: payload: | channel: ${{ vars.SLACK_CHANNEL_ID }} text: >- - *Codeplain Client*: Building the dev build FAILED :x: (commit `${{ github.sha }}`). Nothing was published to PyPI. + *Codeplain Client*: Building the dev build FAILED :x: (commit `${{ env.SHORT_SHA }}`). Nothing was published to PyPI. See <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|the run log>. - name: Notify Slack (failure) @@ -157,7 +160,7 @@ jobs: payload: | channel: ${{ vars.SLACK_CHANNEL_ID }} text: >- - *Codeplain Client*: Publishing dev build to PyPI FAILED :x: (commit `${{ github.sha }}`). + *Codeplain Client*: Publishing dev build to PyPI FAILED :x: (commit `${{ env.SHORT_SHA }}`). See <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|the run log>. # Spell out the --pre install: a plain "pip install codeplain" will