Lightweight DevOps analytics for GitHub Actions.
Scan every repo under an owner/org and see which workflows are slow, flaky, or trending worse over time.
- Live GitHub API — no database, no sync jobs, fully stateless
- Multi-repo — iterates every repo under a user or org
- Analytics — avg / p95 / max duration, failure rate, daily trend
- Insights — human-readable callouts (bottlenecks, regressions, flaky workflows)
- Dashboard — Next.js + Tailwind + Recharts
- Verifiable builds — every production deploy is signed with a SLSA build provenance attestation you can independently verify
- MCP server — use GHAlyzer as a tool inside Claude Desktop, Cursor, Windsurf, etc. via the Model Context Protocol
GHAlyzer exposes an MCP server at https://ghalyzer.app/api/mcp. Ask Claude / Cursor / Windsurf things like "which workflows in vercel/next.js have the worst failure rate this month?" and it will call GHAlyzer directly — no login, public repos only.
Add to ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows):
{
"mcpServers": {
"ghalyzer": {
"url": "https://ghalyzer.app/api/mcp"
}
}
}Add a remote MCP server pointing at https://ghalyzer.app/api/mcp. No command, no args, no auth.
| Tool | Purpose |
|---|---|
list_public_repos |
List public non-archived repos under an owner. |
scan_public_workflows |
Full analytics: avg/p95/max duration, failure rate, daily trend, insights. |
get_public_insights |
Just the high-signal insights (slow/flaky/regressing workflows). |
Rate-limited to 3 scans per 10 min per IP. Capped at 10 repos / 20 runs per call to stay within GitHub's unauthenticated API budget.
Every commit pushed to main produces a signed SLSA v1 build provenance attestation that cryptographically binds the built artifact to this repository and commit. You can verify that ghalyzer.app is serving exactly what's in this repo — no trust in me required.
curl -s https://ghalyzer.app/api/version
# {
# "sha": "abc123…",
# "repo": "arddluma/GHAlyzer",
# "built_at": null
# }The footer of every page shows the same commit SHA, linked to GitHub.
Install the GitHub CLI, then:
# Grab the artifact + attestation for the SHA from step 1
gh run download --repo arddluma/GHAlyzer --name ghalyzer-<sha>
# Verify it was built by this repo's workflow
gh attestation verify ghalyzer-<sha>.tar.gz \
--repo arddluma/GHAlyzer \
--source-ref refs/heads/mainA successful verification looks like:
✓ Verification succeeded!
- Subject: ghalyzer-<sha>.tar.gz
- Predicate: https://slsa.dev/provenance/v1
- Source repo: https://github.com/arddluma/GHAlyzer
- Source commit: <sha>
- Workflow: .github/workflows/deploy.yml@refs/heads/main
- Signer: github-actions
If the site's /api/version SHA matches the artifact SHA you just verified, the live deploy was built from the public source code in this repo, by the workflow defined in .github/workflows/deploy.yml, and not tampered with. See all attestations →.
yarn install
cp .env.example .env
# Register a GitHub App (see below) and fill in the required vars
yarn devOpen http://localhost:3000, click Sign in with GitHub, then install the app on your accounts/orgs. The owner dropdown populates from your installations.
| Var | Required | Description |
|---|---|---|
GITHUB_APP_ID |
yes | GitHub App numeric ID. |
GITHUB_APP_CLIENT_ID |
yes | GitHub App OAuth client ID. |
GITHUB_APP_CLIENT_SECRET |
yes | GitHub App OAuth client secret. |
GITHUB_APP_PRIVATE_KEY |
yes | PEM private key (multi-line, or single-line with \n). |
GITHUB_APP_SLUG |
yes | URL slug (the bit after github.com/apps/ on the install page). |
SESSION_SECRET |
yes | 32 random bytes, base64 (openssl rand -base64 32). Encrypts the session cookie. |
TRUSTED_PROXY |
no | 1 if behind a trusted reverse proxy (Vercel/nginx/Cloudflare) so rate limiting can key on real client IP. |
- Go to https://github.com/settings/apps/new.
- Permissions → Repository:
- Actions: Read-only
- Metadata: Read-only (added automatically)
- Where can this GitHub App be installed?: Any account (or Only on this account for personal use).
- Identifying and authorizing users:
- Callback URL:
http://localhost:3000/api/github/callback - ✅ Request user authorization (OAuth) during installation
- Callback URL:
- Post-installation Setup URL:
http://localhost:3000/api/github/callback - Disable webhooks (we don't use them).
- Create the app. On the app's settings page:
- Note the App ID →
GITHUB_APP_ID - Note the Client ID →
GITHUB_APP_CLIENT_ID - Generate a Client secret →
GITHUB_APP_CLIENT_SECRET - Generate a Private key (downloads a
.pem) → paste its contents intoGITHUB_APP_PRIVATE_KEY - The public page URL is
https://github.com/apps/<slug>— that slug isGITHUB_APP_SLUG
- Note the App ID →
- The GitHub App private key never leaves the server. Installation access tokens are minted on demand (1h lifetime) and scoped to a single owner's installation.
- Scopes requested: Actions: Read-only + Metadata: Read-only. The app literally cannot write to any repo, workflow, or secret.
- User identity is established via a short-lived OAuth code flow; the resulting user-to-server token is used only to list the user's installations. It lives in an AES-256-GCM encrypted, HttpOnly, SameSite=Lax cookie with an 8-hour TTL.
- Every call to
/api/analyticsor/api/reposre-verifies that the signed-in user actually has access to the requested installation (prevents IDOR). - No PAT entry in the UI; no server-side long-lived token; no
GITHUB_OWNERallowlist needed — the authorization model is entirely delegated to GitHub App installations.
- Run behind a trusted reverse proxy (Vercel, nginx, Cloudflare) and set
TRUSTED_PROXY=1so the rate limiter can key on the real client IP. Otherwise the limiter applies a global quota only. - This repo uses Yarn. Install with
yarn install --frozen-lockfile. Do not mixnpm installor you risk a divergent dependency tree. .envis gitignored — never commit your token.
All endpoints require the X-Requested-With: fetch header (anti-CSRF) and a valid session cookie (set via /api/github/login).
GET /api/session— current user{ login, id, avatar_url }or{ user: null }GET /api/installations— installations the signed-in user has access to, plus install URLGET /api/repos?owner=<name>— list repos for an installationGET /api/analytics?owner=<name>&days=14&maxRepos=20&maxRunsPerRepo=100— full analytics payloadPOST /api/github/logout— clear the session cookieGET /api/github/login— kick off OAuth flow (top-level redirect; notfetch-callable)
duration_seconds = updated_at - run_started_at (falls back to created_at). This matches the wall-clock time of each workflow run visible in the Actions UI.
- The tool only reads from GitHub — no writes, no persistence.
- Large orgs: tune
maxReposandmaxRunsPerRepoto stay under the 5k req/hr rate limit.
