← Back to Blog

How to Know Which GitHub Actions Deployment Caused an Incident

The First Question Nobody Can Answer Fast Enough

An alert fires. Error rates are climbing. You open the incident, pull up your metrics, and the first thing you need to know is: what changed?

Someone Slacks the engineering channel asking if anyone deployed in the last couple of hours. Another person opens GitHub Actions to scan through recent workflow runs. A third checks the deployment channel in Slack to see if there are any messages that didn't get a reply. You piece together an answer in ten or fifteen minutes — if you're lucky.

This is the gap between what your monitoring tools give you and what you actually need during incident response. Metrics tell you something broke. They don't tell you why. Deployments are the most common reason things break, and yet most teams treat them as separate from the incident timeline.

The fix is simple and takes about five minutes to implement.

What "Deployment Tracking" Actually Means

Deployment tracking is not a complicated feature. It means logging a structured event each time code ships to production, with enough context that an on-call engineer can immediately see what deployed, when, who triggered it, and which exact commit.

When that event is stored alongside your monitoring data, it shows up in the incident timeline automatically. Instead of asking "did anyone deploy recently?", your on-call engineer opens the incident and sees the answer already there.

Alert24 has a change log API that accepts these events. One HTTP call at the end of your deploy job is all it takes. Everything else — correlating the event with active incidents, surfacing it in the incident context, making it filterable by environment and service — happens on the other side.

Adding the Step to Your GitHub Actions Workflow

Here is the complete step. Drop it at the end of your deploy job, after the actual deployment runs:

- name: Log deployment to Alert24
  if: success()
  env:
    ALERT24_API_KEY: ${{ secrets.ALERT24_API_KEY }}
  run: |
    curl -s -X POST https://api.alert24.com/v1/changes \
      -H "Authorization: Bearer $ALERT24_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "type": "deployment",
        "summary": "Deployed ${{ github.repository }} @ ${{ github.sha }}",
        "environment": "production",
        "actor": "${{ github.actor }}",
        "metadata": {
          "sha": "${{ github.sha }}",
          "ref": "${{ github.ref_name }}",
          "workflow": "${{ github.workflow }}",
          "run_url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
        }
      }'

Two things worth pointing out about this step.

First, the if: success() condition. This is intentional. You only want deployment events for deploys that actually completed. If your deploy job fails partway through, logging a deployment event creates a false signal — an engineer investigating an incident might assume the partial deploy succeeded when it didn't.

Second, the run_url in the metadata. This is the direct link back to the GitHub Actions run that produced the deployment. When your engineer sees the deployment in the incident timeline, they can click through to the exact pipeline run, see which commits were included, read the workflow logs, and check if anything looked unusual. That's the full feedback loop, available without leaving the incident.

What Gets Logged

Every field in the payload serves a purpose during incident response:

Field Value Why It Matters
type "deployment" Distinguishes deploys from config changes, feature flags, rollbacks
summary Repo name + short SHA Human-readable label shown in the timeline
environment "production" Filters out staging noise during a prod incident
actor GitHub username of the person or bot that triggered the run Tells you who to loop in immediately
sha Full commit SHA Enables precise diffing against the previous deployment
ref Branch name Confirms which branch shipped — useful when hotfixes and main-branch deploys coexist
workflow Workflow name Identifies which workflow ran, especially helpful in monorepos
run_url Direct link to the Actions run One click to the full pipeline context

You can extend the metadata with anything else your team finds useful — a PR number, a Jira ticket ID, the version tag from your release process. The metadata object is freeform.

How It Appears in an Incident

When Alert24 creates an incident — whether from an uptime check failure, a triggered alert, or manual creation — the incident detail view includes a change log panel showing events from a configurable lookback window, typically two to four hours before the incident opened.

Your on-call engineer sees something like this in the panel:

  • 16:42 — Deployed api-service @ 8f2a1c3, branch: main (actor: dan)
  • 15:50 — Deployed worker-service @ 3e19b4a, branch: main (actor: CI)
  • 15:10 — Deployed api-service @ c7d0821, branch: hotfix/rate-limiter (actor: priya)

If the error rate started climbing at 16:45, the deployment three minutes earlier is the obvious lead. The engineer is not starting from scratch — they are validating a specific hypothesis. That distinction matters a lot when you're trying to resolve something at 2 AM.

The panel is filterable by environment, so you can scope it to production events only. If you add a service field to your metadata, you can filter further. The more granular your logging, the more useful the correlation.

Handling Multiple Services and Monorepos

If you have multiple services deploying from separate workflow files, add the logging step to each one. The HTTP call is lightweight, and the granularity is worth it.

In a monorepo where one workflow deploys multiple services, you can log separate events per service by running the step in a matrix or in separate jobs:

- name: Log deployment to Alert24
  if: success()
  env:
    ALERT24_API_KEY: ${{ secrets.ALERT24_API_KEY }}
  run: |
    curl -s -X POST https://api.alert24.com/v1/changes \
      -H "Authorization: Bearer $ALERT24_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "type": "deployment",
        "summary": "Deployed ${{ matrix.service }} @ ${{ github.sha }}",
        "environment": "production",
        "actor": "${{ github.actor }}",
        "metadata": {
          "service": "${{ matrix.service }}",
          "sha": "${{ github.sha }}",
          "ref": "${{ github.ref_name }}",
          "run_url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
        }
      }'

During an incident affecting a specific service, you can filter the change log panel to show only events for that service. Ruling out services that didn't deploy in the relevant window is as useful as identifying the one that did.

Beyond Deploys

The same API endpoint accepts other change types: "config_change", "feature_flag", "rollback", "database_migration". If you have automation that enables feature flags, applies database migrations, or modifies infrastructure, logging those events follows the same pattern.

Incidents that look like performance degradations often trace back to a query plan change from a migration that ran an hour before the alert fired. Incidents that seem like traffic spikes often trace back to a feature flag enabling a new code path for a larger user segment. Those connections are invisible unless the events are in the timeline.

Start with deployment tracking. Once you've seen it pay off during an incident, the case for logging other change types writes itself.

Next Steps

Store your Alert24 API key as a repository secret (or an organization-level secret if you want to share it across repos). Add the step to your most critical production deployment workflow first — the service that causes the most incidents when something goes wrong.

Run a test deploy. Open Alert24 and confirm the event appears in the change log. Then add the step to the rest of your deployment workflows.

The next time an incident fires and someone asks "what deployed recently?", that question will already have an answer.