Skip to content
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `prw run` now notifies once (`merged` or `closed`) and automatically stops watching a PR once it is merged or closed, instead of polling it forever. This notification always fires, regardless of `--on`/`notification_filter`.

### Added
- `prw flaky <owner/repo | URL>`: finds flaky GitHub Actions jobs — jobs that failed and then passed when their workflow run was re-run on the same commit — in the repository's last `--runs` workflow runs (default 300, max 1000), grouped by workflow and job with counts, last occurrence and a link to the latest failed attempt; `--json`. Cancelled jobs (fail-fast victims) don't count. New MCP tool `get_flaky_checks`.
- Failed job logs: `prw checks --logs` and `prw wait --logs` print, for each failed GitHub Actions job, the output of the step that failed (its last `--log-lines` lines up to the error, timestamps/colors/runner markup stripped); `failed_logs` / `logs` in `--json`. New MCP tool `get_failed_logs` returns the same to agents, and `get_ci_status` / `wait_for_ci` point to it on failure. Checks from other CI systems are listed with their link, as they have no log in the API.
- `prw checks <PR_URL>`: every check run and commit status of a PR's head commit with its result, duration and link, failures first; `--json` for scripts. Superseded runs are left out. Exits `0` passed, `1` failed, `3` error, `4` no CI, `8` still running.
- Review and merge events: `prw run` notifies when a PR is approved, gets changes requested, runs into merge conflicts, or becomes ready to merge after branch protection was holding it back. Shown in console, native, Slack/Discord and generic webhooks (`type` `pr_review_change` / `pr_merge_state_change`), filtered by `--on` (`fail`: changes requested and conflicts; `success`: approved and ready). `prw list` gains `REVIEW` and `MERGE` columns (`review` / `merge` in `--json` and MCP `list_watched`); `prw wait --json` and the MCP tools report `merge_state`. Disable with `prw config set pr_events false`.
- Auto-watch: `prw watch --mine` follows all your open PRs and `prw watch --review-requested` the PRs where your review is requested, across every repository. Matches are added immediately; `prw run` refreshes the searches every 5 minutes. Review-request PRs are dropped when the request goes away; PRs watched by hand are never touched by it. `prw unwatch --mine` / `--review-requested` turns it off and removes the PRs it added. `prw list` gains an `ADDED BY` column (and `source` in `--json`); `prw config show` shows `auto_watch`.
- `prw mcp`: a Model Context Protocol server over stdio for AI coding agents, with tools `get_ci_status`, `wait_for_ci` (progress notifications, cancellation, fail-fast), `list_watched`, `watch_pr` and `unwatch_pr`. Implemented without new dependencies (the official Go SDK needs Go 1.25).
Expand Down
56 changes: 55 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ The tool is deliberately minimal. It does one thing well: watch PRs and tell you

## Features

- **`prw checks`**: every check of a PR with its result, duration and link — and with `--logs`, the output of the step that failed
- **`prw flaky`**: find a repository's flaky jobs (failed, then passed on re-run of the same commit)
- **`prw wait`**: block until a PR's CI finishes and exit 0/1/2 — for scripts, pipelines and AI coding agents
- **MCP server** (`prw mcp`): lets Claude Code, Claude Desktop, Cursor and other agents check and wait for PR CI as tools
- **Auto-watch**: `prw watch --mine` / `--review-requested` follow your PRs and review requests across all repositories, no URLs needed
Expand Down Expand Up @@ -242,7 +244,7 @@ prw wait "$PR_URL" --timeout 15m --json
| `4` | The PR has **no CI**: no checks or statuses were reported within 3 minutes of its last update (see below) |
| `130` | Interrupted (Ctrl+C) |

It fails fast — as soon as one check fails it exits `1` with the failed checks and their links, without waiting for the rest. If someone pushes to the PR meanwhile, it follows the new head commit. Transient errors (network, 5xx, rate limits) are retried up to 3 times in a row. Polls every 15s by default (`--interval`).
It fails fast — as soon as one check fails it exits `1` with the failed checks and their links, without waiting for the rest. If someone pushes to the PR meanwhile, it follows the new head commit. Transient errors (network, 5xx, rate limits) are retried up to 3 times in a row. Polls every 15s by default (`--interval`). Add `--logs` to also get the output of the failed step of each failed job (see below; `failed_logs` in `--json`).

With `--json`, stdout is a single object:

Expand All @@ -266,6 +268,56 @@ With `--json`, stdout is a single object:

**PRs without CI.** Some PRs never get a single check — the repository has no CI, or none runs for the PR's base branch (for example a stacked PR whose base isn't `main`). Right after a push, though, every PR briefly has zero checks while CI registers them. So prw only concludes `no_ci` once the PR has had no checks or statuses at all **and** hasn't been updated for 3 minutes; until then it reports `pending`. `no_ci` exits with `4`, not `0`, so `prw wait && deploy` doesn't deploy a PR that no CI looked at. `prw run` shows `no_ci` in `prw list` but doesn't send a notification for it, and notifies normally if CI shows up later (e.g. after the PR is retargeted).

## Seeing every check: `prw checks`

`prw checks` lists each check of a PR's head commit (GitHub Actions jobs, other check runs and legacy commit statuses) with its result, how long it took and a link to it — failures first, then running ones. The PR does not need to be on the watch list.

```
$ prw checks https://github.com/owner/repo/pull/123
owner/repo#123 — Add feature
abc1234 · failure · 1 fail, 1 pending, 3 pass · merge: blocked

❌ fail Test (ubuntu-latest) 4m02s https://github.com/owner/repo/actions/runs/…/job/…
⏳ pending E2E 1m10s… https://github.com/owner/repo/actions/runs/…/job/…
✅ pass Build 45s https://github.com/owner/repo/actions/runs/…/job/…
✅ pass Lint 21s https://github.com/owner/repo/actions/runs/…/job/…
✅ pass ci/jenkins https://jenkins.example.com/job/…
```

Runs superseded by a newer run of the same check (e.g. cancelled by `concurrency: cancel-in-progress`) are left out, as they don't count. `--json` prints the same as an object with a `checks` array (`name`, `bucket`, `state`, `kind`, `url`, `started_at`, `completed_at`). Exit codes match `prw wait` — `0` passed (or merged), `1` failed, `3` error or closed without merging, `4` no CI — plus `8` while checks are still running.

### Why did it fail? `--logs`

`prw checks --logs` (and `prw wait --logs`) also print, for each failed GitHub Actions job, the output of the step that failed: its last lines up to the error, with timestamps, colors and runner markup stripped — what you'd scroll to in the browser.

```
$ prw checks https://github.com/owner/repo/pull/123 --logs
…
── Lint · step: staticcheck ./... ──
-: internal error in importing "cmp" (cannot decode "cmp", export data version 5 is greater than maximum supported version 4)
-: internal error in importing "math/bits" (…)
Error: Process completed with exit code 1.
Full log: https://github.com/owner/repo/actions/runs/…/job/…
```

`--log-lines` sets how many lines (default 40). At most 5 job logs are downloaded per command. Checks from other CI systems (and legacy commit statuses) have no log in the API, so you get their link instead. Logs expire after GitHub's retention period (90 days by default). Fine-grained tokens need the **Actions: read** permission.

## Finding flaky checks: `prw flaky`

A job that fails and then passes when its workflow run is re-run — same commit, same code — is flaky. `prw flaky` scans a repository's recent GitHub Actions runs (300 by default, `--runs` up to 1000) for exactly that:

```
$ prw flaky grafana/grafana
grafana/grafana: scanned 300 workflow run(s) since 2026-10-24, 3 of them re-run.

FLAKED WORKFLOW JOB LAST SEEN LATEST FAILED ATTEMPT
1x Backend Unit Tests Grafana Enterprise (3/8) 2026-10-24 15:32 https://github.com/grafana/grafana/actions/runs/…/job/…
1x Integration Tests Sqlite Enterprise (2/16) 2026-10-24 15:25 https://github.com/grafana/grafana/actions/runs/…/job/…
1x End-to-end tests Playwright E2E tests (5/8) 2026-10-24 07:55 https://github.com/grafana/grafana/actions/runs/…/job/…
```

It takes a repository (`owner/repo`) or any GitHub URL in it, such as a PR's. Only re-run runs can tell flakiness apart from real failures, and jobs cancelled because another job failed first don't count. `--json` for scripts. It costs a few requests per 100 runs plus a couple per re-run run; on large repositories GitHub takes a few seconds per request, so prw fetches in parallel.

## Using prw from AI agents: `prw mcp`

`prw mcp` runs a [Model Context Protocol](https://modelcontextprotocol.io) server over stdio, so coding agents can check a PR's CI and wait for it after pushing — and read which checks failed — without screen-scraping `gh` output.
Expand All @@ -291,6 +343,8 @@ Tools:
| Tool | What it does |
|------|--------------|
| `get_ci_status` | Current state of a PR (`success`, `failure`, `error`, `pending`, `merged`, `closed`, `no_ci`), check counts, failed checks with links. Returns immediately. |
| `get_failed_logs` | For each failed GitHub Actions job, the output of the step that failed (last `max_lines` lines up to the error, default 40). What an agent needs to fix the failure without a browser. |
| `get_flaky_checks` | A repository's jobs that failed and then passed on re-run of the same commit, with counts and links — so an agent can tell a known-flaky failure from one its change caused. |
| `wait_for_ci` | Blocks until CI finishes (`timeout_seconds` default 600, max 3600). Fails fast on the first failed check, follows new pushes, sends progress notifications. On timeout it returns `pending` with `timed_out: true` so the agent can call it again. |
| `list_watched` | PRs on the local watch list with their last known state. |
| `watch_pr` / `unwatch_pr` | Add or remove a PR from the watch list (a running `prw run` picks it up). |
Expand Down
6 changes: 5 additions & 1 deletion TASKS.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,11 @@ Legend — Impact: 1–5 · Effort: S (≤½ day), M (1–2 days), L (3+ days)
- ✅ #18 `no_ci` state — done (0 checks + PR not updated for 3 min → `no_ci`; `prw wait` exits 4; no notification; verified live: 11 real PRs went from `pending` to `no_ci`, `prw wait` on one returns in 2s instead of the 30-min timeout)
- ✅ Codex review feedback on #6, #7, #13–#17 — done in `fix/codex-review`: superseded check runs (verified live on zephyrproject-rtos/zephyr#105183: was `failure`, now `success`), head re-check in `prw wait`, context propagation into every API request, closed review-request PRs keep their notification, no `no_ci` flapping, ETag cache eviction, GovSlack.
- ✅ #11 Review & mergeability events — done (approved / changes_requested / conflict / ready; verified live: devblac/prw#12 `ready`, cli/cli#14727 `blocked`, a simulated blocked → ready notified once and ready → blocked stayed silent)
- Next: #12 `prw checks <PR>`
- ✅ Codex follow-ups on #18/#19 — failed head re-check no longer accepted as "unchanged"; review-request PRs only dropped when proven withdrawn (open or 404), never on a cancelled or failed lookup; all review pages read.
- ✅ #12 `prw checks <PR>` — done (buckets fail/cancel/pending/pass/neutral/skipping, durations, links, `--json`, exit 8 while running; verified live on devblac/docker-lint#5, zephyrproject-rtos/zephyr#105183 — 35 checks, superseded run hidden — and devblac/prw#13)
- ✅ #15 Failed job log excerpt — done (`--logs` on `checks`/`wait`, MCP `get_failed_logs`; parser tested on a real staticcheck failure log; verified live on cloudposse/atmos#3476, #3422 and #2199 — a commit status — and over MCP stdio)
- ✅ #16 Flakiness detection — done as `prw flaky` + MCP `get_flaky_checks` (failed-then-passed across attempts of the same run; verified live: grafana/grafana 4 flaky jobs in 300 runs, hashicorp/terraform "Unit Tests"; parallel fetching cut grafana from 24s to 9s). The "status history" half was dropped: low value for its cost.
- Next: #17 packaging (Homebrew/Scoop) + keychain token storage, #8 `gh` extension publishing — both need owner decisions

## Planned

Expand Down
Loading