Skip to content

Instantly share code, notes, and snippets.

@javiertejero
Forked from icaromh/README.md
Last active November 7, 2025 08:23
Show Gist options
  • Select an option

  • Save javiertejero/00e39e3d926c7b4690a207504a4d554e to your computer and use it in GitHub Desktop.

Select an option

Save javiertejero/00e39e3d926c7b4690a207504a4d554e to your computer and use it in GitHub Desktop.

PR Check - Pipeline Status Monitor

Monitors your GitHub Pull Request pipeline and notifies you when all checks pass or any check fails.

(heavily inspired in Queue Notifier by @Gpx)

Note

Currently works only on macOS.

Installation

You need two things:

  1. npx which you can get with Node.js
  2. gh (GitHub CLI) and you must be logged in

Usage

Run:

npx zx https://raw.githubusercontent.com/icaromh/pr-check/main/pr-check.mjs org repo pr

Ok, that's long, so maybe create an alias:

alias prcheck='npx zx https://raw.githubusercontent.com/icaromh/pr-check/main/pr-check.mjs'
prcheck org repo pr

Make it permanent by adding the alias to your shell profile (e.g. ~/.zshrc or ~/.bashrc).

org/repo/pr is your GitHub organization, repository, and pull request number. Example:

prcheck acme-inc better-db 123

If you work often with an organization or a repository you can create more aliases:

alias prcheck='npx zx https://raw.githubusercontent.com/icaromh/pr-check/main/pr-check.mjs'
alias prcheck-acme='prcheck acme-inc'
alias prcheck-bdb='prcheck-acme better-db'

prcheck-acme foo 2 # Will check PR 2 for acme-inc/foo
prcheck-bdb 1 # Will check PR 1 for acme-inc/better-db

Local Usage

If you have the script file locally:

npx zx pr-check.mjs org repo pr

Or with an alias pointing to the local file:

alias prcheck='npx zx /path/to/pr-check.mjs'
prcheck org repo pr

What it does

The script will:

  1. πŸ” Fetch the specified pull request

  2. πŸ•’ Monitor all check runs every 20 seconds

  3. πŸ“Š Display real-time status of each check with emojis:

    • βœ… Success
    • ❌ Failure
    • πŸ•’ In Progress
    • ⏩ Skipped
    • 🚫 Cancelled
    • ⏰ Timed Out
    • ❗ Action Required
  4. ⏰ Track and display elapsed time

  5. πŸ”” Show a macOS notification when complete

  6. πŸ”Š Play a sound notification

  7. Exit with status code:

    • 0 if all checks pass
    • 1 if any check fails

Let the script run. A notification window and sound will alert you when all checks are complete or when a check fails.

Example Output

# Fix bug in authentication flow
πŸ™ https://github.com/acme-inc/better-db/pull/123

Checks:
----------------------------------
βœ…  Build
βœ…  Lint
βœ…  Test
πŸ•’  Deploy Preview

⏳ Not all checks are completed yet. Retrying in 20 seconds...

When complete:

# Fix bug in authentication flow
πŸ™ https://github.com/acme-inc/better-db/pull/123

Checks:
----------------------------------
βœ…  Build
βœ…  Lint
βœ…  Test
βœ…  Deploy Preview

⏰ Took: 3m 42s

----------------------------------
🏁 All checks are completed! πŸŽ‰

βœ… PR #123 - All checks passed successfully

Or if there's a failure:

# Fix bug in authentication flow
πŸ™ https://github.com/acme-inc/better-db/pull/123

Checks:
----------------------------------
βœ…  Build
βœ…  Lint
❌  Test
βœ…  Deploy Preview

----------------------------------
❌ Failures in the pipeline

⏰ Took: 2m 15s

❌ PR #123 has check failures

Requirements

  • macOS (for sound and notification support)
  • Node.js (for npx)
  • GitHub CLI (gh) authenticated with appropriate repository access

Troubleshooting

If you get an error about gh not being installed:

brew install gh
gh auth login

If you get permission errors, make sure your GitHub token has access to the repository:

gh auth status
#!/usr/bin/env zx
/*
* PR Check
* --------
* Author: Icaro Heimig
* Description:
* Watches a GitHub pull request's check runs and notifies
* you when all checks pass or any check fails.
*
* Requirements:
* - Node.js with zx (`npx zx <script>`)
* - GitHub CLI (`gh`) authenticated and available in PATH
* - macOS (uses `afplay` for sound notifications)
*
* Usage:
* npx zx pr-check.mjs <org> <repo> <prNumber>
*
* License: MIT
*/
if ((await which("gh", { nothrow: true })) === null) {
console.error("This script requires the GitHub CLI (gh) to be installed.");
process.exit(1);
}
if (argv._.length !== 3) {
console.error("Invalid input format. Expected format: org repo prNumber");
process.exit(1);
}
const [org, repo, prNumber] = argv._;
const POLLING_TIME = 20_000; // 20 seconds in milliseconds
function clearTerminalScreen() {
console.clear();
process.stdout.write('\x1Bc');
}
function parseCheckConclusion(conclusion) {
const statusMap = {
skipped: '⏩',
success: 'βœ…',
failure: '❌',
cancelled: '🚫',
neutral: 'βšͺ',
timed_out: '⏰',
action_required: '❗',
};
return statusMap[conclusion] || '❓';
}
function parseCheckStatus(status) {
return status === 'in_progress' ? 'πŸ•’' : '⏸️';
}
async function getPullRequest(org, repo, prNumber) {
const result = JSON.parse(
await $`gh api repos/${org}/${repo}/pulls/${prNumber}`
);
return result;
}
async function getCheckRuns(org, repo, sha) {
const result = JSON.parse(
await $`gh api repos/${org}/${repo}/commits/${sha}/check-runs --jq '.check_runs'`
);
return result;
}
async function checkPR(org, repo, prNumber) {
const pullRequest = await getPullRequest(org, repo, prNumber);
const sha = pullRequest.head.sha;
const initialTime = new Date().getTime();
let allChecksCompleted = false;
let hasErrors = false;
let checkRuns = [];
while (!allChecksCompleted && !hasErrors) {
clearTerminalScreen();
checkRuns = await getCheckRuns(org, repo, sha);
// Remove duplicates based on title or name, using completed_at to disambiguate
const checkMap = new Map();
checkRuns.forEach((check) => {
const key = check.output?.title || check.name;
if (!key) return; // Skip entries without title or name
const existing = checkMap.get(key);
if (!existing) {
checkMap.set(key, check);
} else {
// Disambiguate using completed_at - keep the most recent one
const existingCompletedAt = existing.completed_at
? new Date(existing.completed_at).getTime()
: 0;
const currentCompletedAt = check.completed_at
? new Date(check.completed_at).getTime()
: 0;
if (currentCompletedAt > existingCompletedAt) {
checkMap.set(key, check);
}
}
});
checkRuns = Array.from(checkMap.values());
// Check if any check has failed
hasErrors = checkRuns.some((check) =>
['failure', 'cancelled'].includes(check.conclusion)
);
// Check if all checks are completed
allChecksCompleted = checkRuns.every(
(check) => check.status === 'completed' || check.conclusion === 'skipped'
);
// Display PR info
console.log(`# ${pullRequest.title}`);
console.log(`πŸ™ ${pullRequest.html_url}\n`);
console.log(`Checks:\n----------------------------------`);
// Display check statuses
checkRuns.forEach((check) => {
const title = check.output?.title || check.name;
if (check.status === 'in_progress') {
console.log(`${parseCheckStatus('in_progress')} ${title}`);
} else if (check.conclusion) {
console.log(`${parseCheckConclusion(check.conclusion)} ${title}`);
}
});
if (hasErrors) {
console.log('\n----------------------------------');
console.log('❌ Failures in the pipeline\n');
break;
}
if (!allChecksCompleted) {
console.log(
`\n⏳ Not all checks are completed yet. Retrying in ${
POLLING_TIME / 1000
} seconds...`
);
await sleep(POLLING_TIME);
}
}
// Calculate elapsed time
const elapsedSeconds = (new Date().getTime() - initialTime) / 1000;
const elapsedMinutes = Math.floor(elapsedSeconds / 60);
const remainingSeconds = Math.round(elapsedSeconds % 60);
console.log(`⏰ Took: ${elapsedMinutes}m ${remainingSeconds}s\n`);
return { hasErrors, pullRequest };
}
const result = await spinner(
`Checking ${org}/${repo}#${prNumber}`,
async () => await checkPR(org, repo, prNumber)
);
// Play sound
$`afplay /System/Library/Sounds/Glass.aiff`;
if (result.hasErrors) {
console.log(`\n❌ PR #${prNumber} has check failures`);
await $`osascript -e 'display notification "PR #${prNumber} has check failures" with title "Pipeline Failed" sound name "Glass"'`;
process.exit(1);
} else {
console.log('\n----------------------------------');
console.log('🏁 All checks are completed! πŸŽ‰\n');
console.log(`βœ… PR #${prNumber} - All checks passed successfully`);
await $`osascript -e 'display notification "All checks passed for PR #${prNumber}" with title "Pipeline Success" sound name "Glass"'`;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment