Skip to content

ghcli/gh-replay-alerts

Repository files navigation

gh-replay-alerts

Your code scanning alerts got wiped by a re-scan, a migration, or a well-meaning intern. This tool brings them back. One command. No drama.

gh extension install ghcli/gh-replay-alerts
pip install tqdm defusedcsv requests

TL;DR — Copy These, Thank Me Later

# Backup alert states (do this BEFORE you touch anything)
gh replay-alerts list owner/repo --scope repo > backup.csv

# Restore from backup
cat backup.csv | gh replay-alerts owner/repo --scope repo

# Reopen all dismissed alerts
gh replay-alerts list owner/repo --scope repo \
  | sed 's/,dismissed,/,open,/' \
  | gh replay-alerts owner/repo --scope repo

# Migrate triage from GHES to GHEC
gh replay-alerts list owner/repo --scope repo --hostname ghes.corp.com > export.csv
cat export.csv | gh replay-alerts owner/repo --scope repo

# Dry run (see what would happen without changing anything)
cat backup.csv | gh replay-alerts owner/repo --scope repo --debug 2>&1 | grep -E "^(INFO|WARNING)"

# Org-wide backup
gh replay-alerts list my-org --scope org > org-backup.csv

# Show help
gh replay-alerts help

What It Does

Reads a CSV of previous alert states. Fetches current alerts from GitHub. Matches them. PATCHes the mismatches. No magic. Just the API, used correctly.

Matching uses a cascading strategy because the world is messy:

  1. Alert number (primary) — stable across code changes. Extracted from the URL.
  2. File location (fallback) — (path, line, column). For when alert numbers change after a re-scan or tool upgrade.

The summary tells you which strategy matched each alert. You are never guessing.

Commands & Options

Command What it does
gh replay-alerts list <name> [opts] Export alerts as CSV or JSON
gh replay-alerts <name> [opts] Replay states from CSV on stdin
gh replay-alerts help Show usage
Option Applies to Description
--scope repo|org|ent both Scope of the query (default: org)
--state open|resolved both Filter by state
--since DATE|Nd both Only alerts after date (2024-10-08, 7d)
--hostname HOST both GHES hostname (default: github.com)
--debug both Verbose logging
--json list JSON instead of CSV
--raw list Raw API JSON
--quote-all list Quote all CSV fields

What the Output Looks Like

Every run prints a diagnostic summary. No silent failures. No guessing.

INFO:  CSV loaded: 2650 rows, 1 repos, 2650 unique files, 2650 with alert numbers
INFO:  === Replay Summary ===
INFO:  API alerts processed: 12
INFO:  Matched to CSV:      8
INFO:    By alert number:   7
INFO:    By location:       1
INFO:    State already same: 6
INFO:    State changed:      2
INFO:  Unmatched:            4
INFO:    Repo not in CSV:    0
INFO:    Path not in CSV:    1
INFO:    Location mismatch:  3
INFO:  ======================

Zero matches? It tells you why:

WARNING: Zero matches found. Common causes:
WARNING:   1. Alerts were re-generated by a new scan (different alert numbers)
WARNING:   2. CSV repo name doesn't match API repo name
WARNING:   3. CSV was generated from a different branch or scan
WARNING:   → 12 alerts matched repo+path but NOT line/column — code edits shifted locations

Exits with code 1 when CSV has data but nothing matched. Because silent success on total failure is a bug, not a feature.


10 Scenarios Where This Tool Saves Your Day

1. Someone re-ran CodeQL and 2,650 dismissals vanished

The classic. Someone clicked "Re-run all jobs." Your carefully triaged false positives? Back to open. Your weekend plans? Also open to interpretation.

cat backup_before_rescan.csv | gh replay-alerts owner/repo --scope repo

Set up a cron. Future-you will thank present-you:

gh replay-alerts list owner/repo --scope repo > "alerts-$(date +%Y-%m-%d).csv"

2. GHES to GHEC migration wiped all triage

Migration tools move code. They do not move dismissal states. Your security team spent 3 sprints triaging 4,000 alerts. The migration tool said success. The dashboard said 4,000 open. The security team said things we cannot print.

gh replay-alerts list owner/repo --scope repo --hostname ghes.company.com > triage.csv
cat triage.csv | gh replay-alerts owner/repo --scope repo

3. The intern dismissed everything as won't fix

No judgment. We have all been the intern.

gh replay-alerts list owner/repo --scope repo \
  | sed 's/,dismissed,/,open,/' \
  | gh replay-alerts owner/repo --scope repo

4. CodeQL upgrade regenerated all alert numbers

Major version upgrades regenerate alerts with fresh IDs. Your backup CSV has the right triage decisions. The cascading strategy matches by location when numbers change.

cat pre_upgrade_backup.csv | gh replay-alerts owner/repo --scope repo --debug

5. Same 500 alerts across 10 repos

Triage one golden repo. Replay everywhere.

gh replay-alerts list owner/golden-repo --scope repo > golden.csv

for repo in repo-1 repo-2 repo-3 repo-4 repo-5 repo-6 repo-7 repo-8 repo-9; do
  cat golden.csv | gh replay-alerts "owner/$repo" --scope repo
done

6. Audit wants a snapshot before you touch anything

Compliance wants a CSV with timestamps, who dismissed what, and why. They want it yesterday.

gh replay-alerts list owner/repo --scope repo > "audit-$(date +%Y%m%d).csv"

7. Default branch switch reset all alert states

master to main can reset alert states because alerts are branch-scoped.

gh replay-alerts list owner/repo --scope repo > pre-rename.csv
# rename branch
cat pre-rename.csv | gh replay-alerts owner/repo --scope repo

8. Repo deleted and re-created during migration

Same code, fresh alert numbers. Location fallback handles it.

cat old_repo_backup.csv | gh replay-alerts owner/new-repo --scope repo --debug

9. Bulk-dismiss low-severity noise before a release

Clear the deck temporarily. Restore after.

gh replay-alerts list owner/repo --scope repo > pre-release.csv
# dismiss the noise via API
cat pre-release.csv | gh replay-alerts owner/repo --scope repo

10. 3am. Dashboard red. Nobody knows what changed.

You do not know what changed. You do not have time to find out. But you have last night's backup.

cat alerts-2026-03-22.csv | gh replay-alerts owner/repo --scope repo

Installation

gh extension install ghcli/gh-replay-alerts
pip install tqdm defusedcsv requests

Requires GitHub CLI (gh auth login) and Python 3.10+.

License

MIT

About

Replay code scanning alert statuses from a CSV export onto current alerts

Resources

Stars

Watchers

Forks

Releases

Packages

Used by

Contributors

Languages