Where I store my re-usable actions
In here, there is:
Two reusable workflows that handle Slack notifications for GitHub Actions runs. They post a start message when a workflow begins and update it with the final status, timing, and colour when it finishes.
Posts an initial "In Progress" Slack message via chat.postMessage and outputs the timing values needed by slack-notify-end.
| Name | Required | Default | Description |
|---|---|---|---|
channel |
No | C01UGQ98P9U |
Slack channel ID to post to |
| Name | Required | Description |
|---|---|---|
SLACK_BOT_TOKEN |
Yes | Slack bot token with chat:write permission |
| Name | Description |
|---|---|
ts |
Slack message timestamp — pass to slack-notify-end |
start |
Start time as epoch seconds — pass to slack-notify-end |
startfmt |
Start time formatted as ISO-8601 — pass to slack-notify-end |
jobs:
slack-start:
uses: pgmac-net/pg-actions/.github/workflows/slack-notify-start.yml@main
secrets: inherit
run:
needs: [slack-start]
runs-on: self-hosted
steps:
- run: echo "do the work here"
slack-end:
if: always()
needs: [slack-start, run]
uses: pgmac-net/pg-actions/.github/workflows/slack-notify-end.yml@main
with:
ts: ${{ needs.slack-start.outputs.ts }}
start: ${{ needs.slack-start.outputs.start }}
startfmt: ${{ needs.slack-start.outputs.startfmt }}
status: ${{ needs.run.result }}
secrets: inheritFor multi-job workflows where you want the overall status to reflect several parallel jobs:
slack-end:
if: always()
needs: [slack-start, job-a, job-b, job-c]
uses: pgmac-net/pg-actions/.github/workflows/slack-notify-end.yml@main
with:
ts: ${{ needs.slack-start.outputs.ts }}
start: ${{ needs.slack-start.outputs.start }}
startfmt: ${{ needs.slack-start.outputs.startfmt }}
status: ${{ (needs.job-a.result == 'failure' || needs.job-b.result == 'failure' || needs.job-c.result == 'failure') && 'failure' || (needs.job-a.result == 'cancelled' || needs.job-b.result == 'cancelled' || needs.job-c.result == 'cancelled') && 'cancelled' || 'success' }}
secrets: inheritUpdates the Slack message posted by slack-notify-start with the final status, colour, and timing information.
| Name | Required | Default | Description |
|---|---|---|---|
channel |
No | C01UGQ98P9U |
Slack channel ID |
ts |
Yes | — | Slack message timestamp from slack-notify-start |
start |
Yes | — | Start time as epoch seconds from slack-notify-start |
startfmt |
Yes | — | Start time formatted as ISO-8601 from slack-notify-start |
status |
Yes | — | Overall job status: success, failure, or cancelled |
| Name | Required | Description |
|---|---|---|
SLACK_BOT_TOKEN |
Yes | Slack bot token with chat:write permission |
| Status | Slack colour | Icon |
|---|---|---|
success |
good (green) |
✅ |
failure |
danger (red) |
🛑 |
cancelled |
warning (yellow) |
⚠ |
| anything else | grey |
❓ |
Two reusable workflows that run OpenSSF Scorecard security checks against your repository. Results are kept local — nothing is published to the OpenSSF public API.
For public repositories. Runs the full Scorecard check suite, uploads results as an artifact, and uploads the SARIF report to the GitHub Security tab.
Also runs automatically on push to main/master and on a weekly schedule (Sunday midnight AEST) when used directly in pg-actions.
| Name | Required | Default | Description |
|---|---|---|---|
runs-on |
No | ubuntu-latest |
Runner label |
permissions:
security-events: write
contents: read
actions: readjobs:
scorecard:
uses: pgmac-net/pg-actions/.github/workflows/scorecard-public.yml@mainFor private repositories. Runs Scorecard checks and uploads results as an artifact only. The GitHub Security tab upload is skipped (GHAS code scanning requires a paid plan for private repos).
Only callable via workflow_call — no standalone schedule trigger.
| Name | Required | Default | Description |
|---|---|---|---|
runs-on |
No | ubuntu-latest |
Runner label |
permissions:
contents: read
actions: readjobs:
scorecard:
uses: pgmac-net/pg-actions/.github/workflows/scorecard-private.yml@mainThis will:
- create a SBoM of your repo
- Attest this SBoM
- Upload the SBoM as an artefact to your repo
- Upload the SBoM to Dependency Track
- Scan the SBoM for any known vulnerabilities in your dependencies
A second job will then:
- Pull down the SBoM artefact
- Verify the artefact attestation
- Scan the SBoM for any known vulnerabilities in your dependencies
- Save the results (as a SARIF file) and upload to the GHAS Security tab of your repo, making the dependencies known vulnerabilities visible
This is not a SAST of your code, or the dependencies code. It just scans for your dependenices (via the SBoM discovery) and maps your versions against known CVE's.
This is not a version upgrading tool Other tools are better for that. EG:
- DependaBot
- RenovateBot (You may notice some renovatebot stuff in here)
Two workflows that together automate the response to Dependabot security alerts. The orchestrator (dependabot-alert.yml) is adopted per repo — copied in by hand, see below — and fans out to the reusable worker (dependabot-management.yml) once per high/critical open alert.
Adoption is manual and currently limited to this repo. An earlier design distributed the orchestrator via
terraform-github, but that was never wired up and the dead configuration has since been removed (pgmac-net/terraform-github#20). Any repo that wants this pipeline needs the file copied in.
For each alert the system will:
- Check the repo for an open issue matching the GHSA ID — skip if found
- Create a GitHub issue in the affected repo, labelled
security,dependenciesandseverity:<level> - Check for an existing fix PR — skip if found
- Run Claude Code on a new branch to update the vulnerable package and lock files
- Open a draft PR that closes the issue on merge
The per-repo orchestrator. Fetches all open high/critical Dependabot alerts and calls dependabot-management.yml once per alert via a matrix strategy. The copy in this repo is workflow_dispatch only; the snippet below adds the 6-hourly schedule that a real adopting repo wants.
This workflow is not callable — copy it directly into each repo's .github/workflows/ directory. It requires no configuration; all secrets flow through via secrets: inherit.
permissions:
contents: write
issues: write
pull-requests: write| Name | Required | Description |
|---|---|---|
CLAUDE_CODE_OAUTH_TOKEN |
No | Claude Code OAuth token — PR creation is skipped if absent |
# .github/workflows/dependabot-alert.yml
name: Dependabot Alert Management
on:
schedule:
- cron: '0 */6 * * *'
workflow_dispatch:
permissions:
contents: write
issues: write
pull-requests: write
jobs:
get-alerts:
name: List High/Critical Alerts
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.alerts.outputs.matrix }}
has_alerts: ${{ steps.alerts.outputs.has_alerts }}
steps:
- name: Fetch open high/critical Dependabot alerts
id: alerts
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
run: |
ALERTS=$(gh api "/repos/${REPO}/dependabot/alerts" \
--jq '[.[] | select(.state == "open") | select(.security_vulnerability.severity | test("^(high|critical)$"))]')
COUNT=$(echo "$ALERTS" | jq 'length')
echo "Found ${COUNT} high/critical open alert(s)"
if [ "$COUNT" -eq 0 ]; then
echo "has_alerts=false" >> "$GITHUB_OUTPUT"
echo "matrix={\"include\":[]}" >> "$GITHUB_OUTPUT"
else
echo "has_alerts=true" >> "$GITHUB_OUTPUT"
MATRIX=$(echo "$ALERTS" | jq -c '{include: [.[] | {
alert_number: (.number | tostring),
package_name: .dependency.package.name,
package_ecosystem: .dependency.package.ecosystem,
severity: .security_vulnerability.severity,
summary: .security_advisory.summary,
cve_id: (.security_advisory.cve_id // ""),
ghsa_id: .security_advisory.ghsa_id,
vulnerable_version_range: .security_vulnerability.vulnerable_version_range,
first_patched_version: (.security_vulnerability.first_patched_version.identifier // "")
}]}')
echo "matrix=${MATRIX}" >> "$GITHUB_OUTPUT"
fi
manage:
name: Process Alert
needs: get-alerts
if: needs.get-alerts.outputs.has_alerts == 'true'
strategy:
matrix: ${{ fromJSON(needs.get-alerts.outputs.matrix) }}
fail-fast: false
uses: pgmac-net/pg-actions/.github/workflows/dependabot-management.yml@main
with:
alert_number: ${{ matrix.alert_number }}
package_name: ${{ matrix.package_name }}
package_ecosystem: ${{ matrix.package_ecosystem }}
severity: ${{ matrix.severity }}
summary: ${{ matrix.summary }}
cve_id: ${{ matrix.cve_id }}
ghsa_id: ${{ matrix.ghsa_id }}
vulnerable_version_range: ${{ matrix.vulnerable_version_range }}
first_patched_version: ${{ matrix.first_patched_version }}
secrets: inheritThe reusable worker. Called once per alert by dependabot-alert.yml. Creates the GitHub issue and (when CLAUDE_CODE_OAUTH_TOKEN is available) opens a draft fix PR via Claude Code.
Can also be called directly from any workflow that already has alert metadata.
| Name | Required | Default | Description |
|---|---|---|---|
alert_number |
Yes | — | Dependabot alert number |
package_name |
Yes | — | Vulnerable package name |
package_ecosystem |
Yes | — | Package ecosystem (npm, pip, maven, etc.) |
severity |
Yes | — | critical or high |
summary |
Yes | — | Short vulnerability description |
cve_id |
No | "" |
CVE identifier if available |
ghsa_id |
Yes | — | GitHub Security Advisory ID (used as the dedup key) |
vulnerable_version_range |
Yes | — | Affected version range |
first_patched_version |
No | "" |
First version containing the fix |
| Name | Required | Description |
|---|---|---|
CLAUDE_CODE_OAUTH_TOKEN |
No | Claude Code OAuth token — PR creation is skipped if absent; manual fix is required |
jobs:
manage:
uses: pgmac-net/pg-actions/.github/workflows/dependabot-management.yml@main
with:
alert_number: "42"
package_name: "lodash"
package_ecosystem: "npm"
severity: "critical"
summary: "Prototype Pollution in lodash"
ghsa_id: "GHSA-jf85-cpcp-j695"
cve_id: "CVE-2019-10744"
vulnerable_version_range: "< 4.17.12"
first_patched_version: "4.17.12"
secrets: inheritThe GitHub issue is still created. The fix PR step is skipped and the issue URL is printed to the job log for manual action.
A workflow that listens for @claude mentions in issues, PR comments, and PR reviews and invokes Claude Code to respond.
Distribute claude.yml to each repo that should respond to @claude mentions. No configuration is needed beyond the two secrets below.
| Name | Required | Description |
|---|---|---|
CLAUDE_CODE_OAUTH_TOKEN |
Yes | Claude Code OAuth token — generate with claude setup-token |
permissions:
contents: write
pull-requests: write
issues: write
id-token: write
actions: read| Event | Condition |
|---|---|
issue_comment |
Comment body contains @claude |
pull_request_review_comment |
Comment body contains @claude |
pull_request_review |
Review body contains @claude |
issues (opened, assigned) |
Issue title or body contains @claude |
# .github/workflows/claude.yml
name: Claude Code
on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
issues:
types: [opened, assigned]
pull_request_review:
types: [submitted]
jobs:
claude:
if: |
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
issues: write
id-token: write
actions: read
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 1
- name: Install Claude Code
run: |
npm install -g @anthropic-ai/claude-code
echo "CLAUDE_BIN=$(which claude)" >> "$GITHUB_ENV"
- name: Run Claude Code
uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
path_to_claude_code_executable: ${{ env.CLAUDE_BIN }}
additional_permissions: |
actions: readclaude setup-token
# copies the token to stdout — add it as a repo or org secret named CLAUDE_CODE_OAUTH_TOKENThe token maps to your Claude Pro/Max subscription quota, not an API key. Org-level secrets only reach public repos on the free GitHub tier; private repos need the secret set individually.