Skip to content
Latchkey

GitHub Slack Notifications workflow (aws/bedrock-agentcore-starter-toolkit)

The GitHub Slack Notifications workflow from aws/bedrock-agentcore-starter-toolkit, explained and optimized by Latchkey.

B

CI health: B - good

Point runs-on at Latchkey and get job timeouts, SHA-pinned actions, self-healing for flaky steps, and up to 58% lower cost, applied automatically.

Grade your own workflow free or run it on Latchkey →
Source: aws/bedrock-agentcore-starter-toolkit.github/workflows/github-slack-notifications.ymlLicense Apache-2.0View source

What it does

This is the GitHub Slack Notifications workflow from the aws/bedrock-agentcore-starter-toolkit repository, a real project running GitHub Actions. It is shown here with attribution under its Apache-2.0 license.

Below, Latchkey shows a faster, safer version produced by its optimization engine.

The workflow

workflow (.yml)
name: GitHub Slack Notifications

# Central GitHub -> Slack integration point. Two triggers feed one shared
# Slack workflow (via SLACK_WEBHOOK_URL), which branches on event_type:
#   - issues opened           -> notify oncall of a new issue
#   - comments on closed PRs  -> redirect the commenter to open an issue,
#                                and notify oncall (closed-PR comments are
#                                otherwise easy to miss)
# Every payload sends the same key set so the Slack workflow can branch
# reliably; fields that don't apply to an event are sent empty.

on:
  issues:
    types: [opened]
  issue_comment:
    types: [created]

jobs:
  notify-issue-opened:
    if: github.event_name == 'issues'
    runs-on: ubuntu-latest
    permissions: {}
    steps:
      - name: Send issue details to Slack
        # Attacker-controlled fields are passed through env: rather than
        # interpolated into the YAML payload, to prevent workflow injection.
        # For issue_opened, the issue_* fields carry the data and the
        # pr_*/comment_* fields are empty.
        env:
          REPOSITORY: ${{ github.repository }}
          CREATED_AT: ${{ github.event.issue.created_at }}
          ISSUE_NUMBER: ${{ github.event.issue.number }}
          ISSUE_TITLE: ${{ github.event.issue.title }}
          ISSUE_URL: ${{ github.event.issue.html_url }}
          ISSUE_AUTHOR: ${{ github.event.issue.user.login }}
          ISSUE_BODY: ${{ github.event.issue.body }}
          LABELS: ${{ join(github.event.issue.labels.*.name, ', ') }}
        uses: slackapi/slack-github-action@v2.0.0
        with:
          webhook: ${{ secrets.SLACK_WEBHOOK_URL }}
          webhook-type: webhook-trigger
          payload: |
            event_type: "issue_opened"
            repository: "${{ env.REPOSITORY }}"
            created_at: "${{ env.CREATED_AT }}"
            issue_number: "${{ env.ISSUE_NUMBER }}"
            issue_title: ${{ toJSON(env.ISSUE_TITLE) }}
            issue_url: "${{ env.ISSUE_URL }}"
            issue_author: "${{ env.ISSUE_AUTHOR }}"
            issue_body: ${{ toJSON(env.ISSUE_BODY) }}
            labels: ${{ toJSON(env.LABELS) }}
            pr_number: ""
            pr_title: ""
            pr_url: ""
            pr_author: ""
            pr_state: ""
            pr_closed_at: ""
            pr_merged_at: ""
            comment_id: ""
            comment_url: ""
            comment_author: ""
            comment_body: ""

  closed-pr-comment-redirect:
    # Only fire on comments left on PRs (issue_comment fires for issues too)
    # that are already closed, and skip comments left by bots.
    if: >-
      github.event_name == 'issue_comment' && github.event.issue.pull_request != null && github.event.issue.state ==
      'closed' && github.event.comment.user.type != 'Bot'
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write
      issues: read
    # Serialize per-PR so the marker-comment dedup is race-free (otherwise two
    # rapid-fire comments could both see "no marker" and both post a redirect).
    # cancel-in-progress is false so the second run still executes after the
    # first finishes -- we want to evaluate dedup against the just-posted marker.
    concurrency:
      group: closed-pr-comment-${{ github.event.issue.number }}
      cancel-in-progress: false
    steps:
      - name: Check commenter permission
        id: perm
        uses: actions/github-script@v9
        with:
          script: |
            // External users on private repos can 404 here; treat any
            // failure as "not a maintainer" so the redirect still fires.
            let permission = 'none';
            try {
              const { data } = await github.rest.repos.getCollaboratorPermissionLevel({
                owner: context.repo.owner,
                repo: context.repo.repo,
                username: context.payload.comment.user.login,
              });
              permission = data.permission;
            } catch (err) {
              core.info(`Permission lookup failed for ${context.payload.comment.user.login}: ${err.message}. Treating as non-maintainer.`);
            }
            const skip = ['admin', 'maintain', 'write'].includes(permission);
            core.setOutput('skip', String(skip));
            core.info(`Commenter ${context.payload.comment.user.login} permission=${permission} skip=${skip}`);

      - name: Check for existing redirect comment
        id: existing
        if: steps.perm.outputs.skip != 'true'
        uses: actions/github-script@v9
        with:
          script: |
            // Marker we embed in our reply so we don't double-post on the same PR.
            const marker = '<!-- closed-pr-comment-redirect -->';
            const comments = await github.paginate(
              github.rest.issues.listComments,
              {
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: context.payload.issue.number,
                per_page: 100,
              }
            );
            const alreadyPosted = comments.some(c => c.body && c.body.includes(marker));
            core.setOutput('already_posted', String(alreadyPosted));

      - name: Post redirect comment
        if: steps.perm.outputs.skip != 'true' && steps.existing.outputs.already_posted != 'true'
        # The Slack notification is the load-bearing part of this job. If
        # posting the bot reply fails (rate limit, transient error), don't
        # block the Slack notification.
        continue-on-error: true
        uses: actions/github-script@v9
        env:
          # Repos with issue templates use /issues/new/choose; repos without
          # templates should change this to /issues/new.
          ISSUES_NEW_URL: https://github.com/${{ github.repository }}/issues/new/choose
        with:
          script: |
            const commenter = context.payload.comment.user.login;
            const issuesNewUrl = process.env.ISSUES_NEW_URL;
            const body = [
              '<!-- closed-pr-comment-redirect -->',
              '',
              `Thanks for the report, @${commenter} - feedback like this is exactly`,
              "how we catch the things we missed. Because this PR is already",
              "closed, the team won't see follow-up comments here.",
              '',
              'Would you mind opening a new issue so we can track it properly?',
              issuesNewUrl,
              '',
              'If this is a security issue, please report it privately via',
              'https://aws.amazon.com/security/vulnerability-reporting/ instead',
              'of a public issue.',
            ].join('\n');

            await github.rest.issues.createComment({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.payload.issue.number,
              body,
            });

      - name: Compute PR state
        id: pr_state
        if: steps.perm.outputs.skip != 'true'
        uses: actions/github-script@v9
        with:
          script: |
            // PRs surface as `issue` events; merged_at is null when closed-not-merged.
            const mergedAt = context.payload.issue.pull_request &&
              context.payload.issue.pull_request.merged_at;
            core.setOutput('state', mergedAt ? 'merged' : 'closed');

      - name: Notify Slack
        # Notify oncall only on the FIRST external comment per PR (gated by
        # already_posted). Subsequent comments on the same PR don't notify --
        # the redirect comment has already directed the commenter to open an
        # issue, and issues notify oncall via the issue path. This bounds
        # notification volume regardless of how chatty a thread becomes.
        if: steps.perm.outputs.skip != 'true' && steps.existing.outputs.already_posted != 'true'
        # Attacker-controlled fields are passed through env: rather than
        # interpolated into the YAML payload, to prevent workflow injection.
        # For closed-PR comments, the issue_* fields are empty (this isn't
        # an issue) and the pr_*/comment_* fields carry the real data.
        env:
          REPOSITORY: ${{ github.repository }}
          CREATED_AT: ${{ github.event.comment.created_at }}
          PR_NUMBER: ${{ github.event.issue.number }}
          PR_TITLE: ${{ github.event.issue.title }}
          PR_URL: ${{ github.event.issue.html_url }}
          PR_AUTHOR: ${{ github.event.issue.user.login }}
          PR_CLOSED_AT: ${{ github.event.issue.closed_at }}
          PR_MERGED_AT: ${{ github.event.issue.pull_request.merged_at }}
          COMMENT_ID: ${{ github.event.comment.id }}
          COMMENT_URL: ${{ github.event.comment.html_url }}
          COMMENT_AUTHOR: ${{ github.event.comment.user.login }}
          COMMENT_BODY: ${{ github.event.comment.body }}
        uses: slackapi/slack-github-action@v2.0.0
        with:
          webhook: ${{ secrets.SLACK_WEBHOOK_URL }}
          webhook-type: webhook-trigger
          payload: |
            event_type: "closed_pr_comment"
            repository: "${{ env.REPOSITORY }}"
            created_at: "${{ env.CREATED_AT }}"
            issue_number: ""
            issue_title: ""
            issue_url: ""
            issue_author: ""
            issue_body: ""
            labels: ""
            pr_number: "${{ env.PR_NUMBER }}"
            pr_title: ${{ toJSON(env.PR_TITLE) }}
            pr_url: "${{ env.PR_URL }}"
            pr_author: "${{ env.PR_AUTHOR }}"
            pr_state: "${{ steps.pr_state.outputs.state }}"
            pr_closed_at: "${{ env.PR_CLOSED_AT }}"
            pr_merged_at: "${{ env.PR_MERGED_AT }}"
            comment_id: "${{ env.COMMENT_ID }}"
            comment_url: "${{ env.COMMENT_URL }}"
            comment_author: "${{ env.COMMENT_AUTHOR }}"
            comment_body: ${{ toJSON(env.COMMENT_BODY) }}

The same workflow, on Latchkey

Removes redundant runs and caps runaway jobs. Added and changed lines are highlighted.

name: GitHub Slack Notifications
 
# Central GitHub -> Slack integration point. Two triggers feed one shared
# Slack workflow (via SLACK_WEBHOOK_URL), which branches on event_type:
#   - issues opened           -> notify oncall of a new issue
#   - comments on closed PRs  -> redirect the commenter to open an issue,
#                                and notify oncall (closed-PR comments are
#                                otherwise easy to miss)
# Every payload sends the same key set so the Slack workflow can branch
# reliably; fields that don't apply to an event are sent empty.
 
on:
  issues:
    types: [opened]
  issue_comment:
    types: [created]
 
jobs:
  notify-issue-opened:
    timeout-minutes: 30
    if: github.event_name == 'issues'
    runs-on: latchkey-small
    permissions: {}
    steps:
      - name: Send issue details to Slack
        # Attacker-controlled fields are passed through env: rather than
        # interpolated into the YAML payload, to prevent workflow injection.
        # For issue_opened, the issue_* fields carry the data and the
        # pr_*/comment_* fields are empty.
        env:
          REPOSITORY: ${{ github.repository }}
          CREATED_AT: ${{ github.event.issue.created_at }}
          ISSUE_NUMBER: ${{ github.event.issue.number }}
          ISSUE_TITLE: ${{ github.event.issue.title }}
          ISSUE_URL: ${{ github.event.issue.html_url }}
          ISSUE_AUTHOR: ${{ github.event.issue.user.login }}
          ISSUE_BODY: ${{ github.event.issue.body }}
          LABELS: ${{ join(github.event.issue.labels.*.name, ', ') }}
        uses: slackapi/slack-github-action@v2.0.0
        with:
          webhook: ${{ secrets.SLACK_WEBHOOK_URL }}
          webhook-type: webhook-trigger
          payload: |
            event_type: "issue_opened"
            repository: "${{ env.REPOSITORY }}"
            created_at: "${{ env.CREATED_AT }}"
            issue_number: "${{ env.ISSUE_NUMBER }}"
            issue_title: ${{ toJSON(env.ISSUE_TITLE) }}
            issue_url: "${{ env.ISSUE_URL }}"
            issue_author: "${{ env.ISSUE_AUTHOR }}"
            issue_body: ${{ toJSON(env.ISSUE_BODY) }}
            labels: ${{ toJSON(env.LABELS) }}
            pr_number: ""
            pr_title: ""
            pr_url: ""
            pr_author: ""
            pr_state: ""
            pr_closed_at: ""
            pr_merged_at: ""
            comment_id: ""
            comment_url: ""
            comment_author: ""
            comment_body: ""
 
  closed-pr-comment-redirect:
    timeout-minutes: 30
    # Only fire on comments left on PRs (issue_comment fires for issues too)
    # that are already closed, and skip comments left by bots.
    if: >-
      github.event_name == 'issue_comment' && github.event.issue.pull_request != null && github.event.issue.state ==
      'closed' && github.event.comment.user.type != 'Bot'
    runs-on: latchkey-small
    permissions:
      pull-requests: write
      issues: read
    # Serialize per-PR so the marker-comment dedup is race-free (otherwise two
    # rapid-fire comments could both see "no marker" and both post a redirect).
    # cancel-in-progress is false so the second run still executes after the
    # first finishes -- we want to evaluate dedup against the just-posted marker.
    concurrency:
      group: closed-pr-comment-${{ github.event.issue.number }}
      cancel-in-progress: false
    steps:
      - name: Check commenter permission
        id: perm
        uses: actions/github-script@v9
        with:
          script: |
            // External users on private repos can 404 here; treat any
            // failure as "not a maintainer" so the redirect still fires.
            let permission = 'none';
            try {
              const { data } = await github.rest.repos.getCollaboratorPermissionLevel({
                owner: context.repo.owner,
                repo: context.repo.repo,
                username: context.payload.comment.user.login,
              });
              permission = data.permission;
            } catch (err) {
              core.info(`Permission lookup failed for ${context.payload.comment.user.login}: ${err.message}. Treating as non-maintainer.`);
            }
            const skip = ['admin', 'maintain', 'write'].includes(permission);
            core.setOutput('skip', String(skip));
            core.info(`Commenter ${context.payload.comment.user.login} permission=${permission} skip=${skip}`);
 
      - name: Check for existing redirect comment
        id: existing
        if: steps.perm.outputs.skip != 'true'
        uses: actions/github-script@v9
        with:
          script: |
            // Marker we embed in our reply so we don't double-post on the same PR.
            const marker = '<!-- closed-pr-comment-redirect -->';
            const comments = await github.paginate(
              github.rest.issues.listComments,
              {
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: context.payload.issue.number,
                per_page: 100,
              }
            );
            const alreadyPosted = comments.some(c => c.body && c.body.includes(marker));
            core.setOutput('already_posted', String(alreadyPosted));
 
      - name: Post redirect comment
        if: steps.perm.outputs.skip != 'true' && steps.existing.outputs.already_posted != 'true'
        # The Slack notification is the load-bearing part of this job. If
        # posting the bot reply fails (rate limit, transient error), don't
        # block the Slack notification.
        continue-on-error: true
        uses: actions/github-script@v9
        env:
          # Repos with issue templates use /issues/new/choose; repos without
          # templates should change this to /issues/new.
          ISSUES_NEW_URL: https://github.com/${{ github.repository }}/issues/new/choose
        with:
          script: |
            const commenter = context.payload.comment.user.login;
            const issuesNewUrl = process.env.ISSUES_NEW_URL;
            const body = [
              '<!-- closed-pr-comment-redirect -->',
              '',
              `Thanks for the report, @${commenter} - feedback like this is exactly`,
              "how we catch the things we missed. Because this PR is already",
              "closed, the team won't see follow-up comments here.",
              '',
              'Would you mind opening a new issue so we can track it properly?',
              issuesNewUrl,
              '',
              'If this is a security issue, please report it privately via',
              'https://aws.amazon.com/security/vulnerability-reporting/ instead',
              'of a public issue.',
            ].join('\n');
 
            await github.rest.issues.createComment({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.payload.issue.number,
              body,
            });
 
      - name: Compute PR state
        id: pr_state
        if: steps.perm.outputs.skip != 'true'
        uses: actions/github-script@v9
        with:
          script: |
            // PRs surface as `issue` events; merged_at is null when closed-not-merged.
            const mergedAt = context.payload.issue.pull_request &&
              context.payload.issue.pull_request.merged_at;
            core.setOutput('state', mergedAt ? 'merged' : 'closed');
 
      - name: Notify Slack
        # Notify oncall only on the FIRST external comment per PR (gated by
        # already_posted). Subsequent comments on the same PR don't notify --
        # the redirect comment has already directed the commenter to open an
        # issue, and issues notify oncall via the issue path. This bounds
        # notification volume regardless of how chatty a thread becomes.
        if: steps.perm.outputs.skip != 'true' && steps.existing.outputs.already_posted != 'true'
        # Attacker-controlled fields are passed through env: rather than
        # interpolated into the YAML payload, to prevent workflow injection.
        # For closed-PR comments, the issue_* fields are empty (this isn't
        # an issue) and the pr_*/comment_* fields carry the real data.
        env:
          REPOSITORY: ${{ github.repository }}
          CREATED_AT: ${{ github.event.comment.created_at }}
          PR_NUMBER: ${{ github.event.issue.number }}
          PR_TITLE: ${{ github.event.issue.title }}
          PR_URL: ${{ github.event.issue.html_url }}
          PR_AUTHOR: ${{ github.event.issue.user.login }}
          PR_CLOSED_AT: ${{ github.event.issue.closed_at }}
          PR_MERGED_AT: ${{ github.event.issue.pull_request.merged_at }}
          COMMENT_ID: ${{ github.event.comment.id }}
          COMMENT_URL: ${{ github.event.comment.html_url }}
          COMMENT_AUTHOR: ${{ github.event.comment.user.login }}
          COMMENT_BODY: ${{ github.event.comment.body }}
        uses: slackapi/slack-github-action@v2.0.0
        with:
          webhook: ${{ secrets.SLACK_WEBHOOK_URL }}
          webhook-type: webhook-trigger
          payload: |
            event_type: "closed_pr_comment"
            repository: "${{ env.REPOSITORY }}"
            created_at: "${{ env.CREATED_AT }}"
            issue_number: ""
            issue_title: ""
            issue_url: ""
            issue_author: ""
            issue_body: ""
            labels: ""
            pr_number: "${{ env.PR_NUMBER }}"
            pr_title: ${{ toJSON(env.PR_TITLE) }}
            pr_url: "${{ env.PR_URL }}"
            pr_author: "${{ env.PR_AUTHOR }}"
            pr_state: "${{ steps.pr_state.outputs.state }}"
            pr_closed_at: "${{ env.PR_CLOSED_AT }}"
            pr_merged_at: "${{ env.PR_MERGED_AT }}"
            comment_id: "${{ env.COMMENT_ID }}"
            comment_url: "${{ env.COMMENT_URL }}"
            comment_author: "${{ env.COMMENT_AUTHOR }}"
            comment_body: ${{ toJSON(env.COMMENT_BODY) }}
 

What changed

1 third-party action is referenced by a movable tag. Pin it to the commit SHA (Latchkey resolves and applies this automatically) so a repointed tag cannot change what runs.

This workflow runs 2 jobs per trigger. On Latchkey the same minutes cost up to 58% less than GitHub-hosted, with zero queue time.

Actions used in this workflow