From c19c8f6c4affdf1f2f6830198959079d813506dd Mon Sep 17 00:00:00 2001 From: Goran Dodig Date: Tue, 27 Oct 2026 18:54:54 +0100 Subject: [PATCH 1/2] ci(release): thread Slack release notices under the start notice Result notices reply in the start notice thread via its ts. Failures are also broadcast to the channel. If the start post never happened, thread_ts is null and notices post to the channel as before. --- .github/workflows/publish-to-pypi.yml | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index cd800291..b4377b78 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -14,6 +14,9 @@ jobs: runs-on: ubuntu-latest permissions: contents: write + outputs: + # The notify job replies in the start notice's thread. + slack-ts: ${{ steps.slack-start.outputs.ts }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -23,6 +26,7 @@ jobs: # Non-fatal: Slack being down must not block the release. - name: Notify Slack (start) + id: slack-start if: ${{ vars.SLACK_CHANNEL_ID != '' }} continue-on-error: true uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v4.0.0 @@ -93,6 +97,13 @@ jobs: if: ${{ !cancelled() && vars.SLACK_CHANNEL_ID != '' }} runs-on: ubuntu-latest + env: + # Notices reply in the start notice's thread, quoted so YAML keeps the ts a + # string. Without a start notice (its post failed or never ran) this is null, + # which the Slack client drops, and they post to the channel. Failures are + # also broadcast to the channel; success stays in the thread. + THREAD_TS: ${{ needs.build.outputs.slack-ts && format('"{0}"', needs.build.outputs.slack-ts) || 'null' }} + steps: - name: Notify Slack (build failure) if: ${{ needs.build.result == 'failure' }} @@ -103,6 +114,8 @@ jobs: errors: true payload: | channel: ${{ vars.SLACK_CHANNEL_ID }} + thread_ts: ${{ env.THREAD_TS }} + reply_broadcast: ${{ env.THREAD_TS != 'null' }} 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>. @@ -116,6 +129,8 @@ jobs: errors: true payload: | channel: ${{ vars.SLACK_CHANNEL_ID }} + thread_ts: ${{ env.THREAD_TS }} + reply_broadcast: ${{ env.THREAD_TS != 'null' }} 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>. @@ -141,6 +156,7 @@ jobs: errors: true payload: | channel: ${{ vars.SLACK_CHANNEL_ID }} + thread_ts: ${{ env.THREAD_TS }} 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 }}`. @@ -154,6 +170,7 @@ jobs: errors: true payload: | channel: ${{ vars.SLACK_CHANNEL_ID }} + thread_ts: ${{ env.THREAD_TS }} text: >- *Codeplain Client*: `${{ github.ref_name }}` published to PyPI :white_check_mark:. Install with `pip install --upgrade codeplain`. @@ -165,7 +182,7 @@ jobs: # # keeps its minimal permissions and no checkout. # linear-release: # name: Record release in Linear - # needs: publish-to-pypi + # needs: [build, publish-to-pypi] # runs-on: ubuntu-latest # # permissions: @@ -192,6 +209,9 @@ jobs: # # outcome — `failure()` would never fire. # - name: Notify Slack (release not recorded) # if: ${{ steps.linear.outcome == 'failure' && vars.SLACK_CHANNEL_ID != '' }} + # env: + # # See the notify job. + # THREAD_TS: ${{ needs.build.outputs.slack-ts && format('"{0}"', needs.build.outputs.slack-ts) || 'null' }} # uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v4.0.0 # with: # method: chat.postMessage @@ -199,6 +219,7 @@ jobs: # errors: true # payload: | # channel: ${{ vars.SLACK_CHANNEL_ID }} + # thread_ts: ${{ env.THREAD_TS }} # 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 26bdc3172f0f562e42e4464e6eee2e0770500c54 Mon Sep 17 00:00:00 2001 From: Goran Dodig Date: Thu, 29 Oct 2026 16:44:07 +0100 Subject: [PATCH 2/2] =?UTF-8?q?ci(release):=20add=20a=20=E2=9C=85=20to=20t?= =?UTF-8?q?he=20start=20notice=20on=20success?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Success replies stay in the thread, so a reaction marks the top-level message too. It needs the reactions:write scope on the Slack app. The step is non-fatal until that scope is granted. --- .github/workflows/publish-to-pypi.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index b4377b78..223ef34a 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -175,6 +175,22 @@ jobs: *Codeplain Client*: `${{ github.ref_name }}` published to PyPI :white_check_mark:. Install with `pip install --upgrade codeplain`. + # Success also puts a ✅ on the start notice, so the result shows in the + # channel without another message. Needs the app's reactions:write scope. + # Non-fatal: the result is already posted, and a re-run gets already_reacted. + - name: Notify Slack (✅ on the start notice) + if: ${{ needs.publish-to-pypi.result == 'success' && env.THREAD_TS != 'null' }} + continue-on-error: true + uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v4.0.0 + with: + method: reactions.add + token: ${{ secrets.SLACK_BOT_TOKEN }} + errors: true + payload: | + channel: ${{ vars.SLACK_CHANNEL_ID }} + timestamp: ${{ env.THREAD_TS }} + name: white_check_mark + # Release pipelines to be turned on only after the rest of the infrastructure # is ready. #