# python-stale-pr-notify-github-action

A GitHub Action to notify users of stale pull requests in a repository. application_family: devops

Scans all open PRs in `theorchard/terraform-infra` and notifies authors when their PRs need attention. Runs daily via a GitHub Actions workflow (`pr_stale.yml`) in the consuming repository.

Uses docker to isolate dependencies and ensure consistent environments across local and CI runs. The script is idempotent and safe to run multiple times without spamming PRs, thanks to a comment lease mechanism.

### What it does

For each open PR that hasn't been updated in 7+ days, it posts a single polite
reminder asking the author to check whether the changes are still needed and either
merge or close the PR (with a best-effort Slack DM to the author). It does **not**
inspect approval, Atlantis apply history, or merge state — it just nudges the author,
who decides what to do.

It skips a PR when:

- the PR carries a `Dont Merge` or `do not apply` label (intentional opt-out), or
- the bot already commented within the last 3 days (`COMMENT_LEASE_DAYS` spam guard).

Otherwise it re-nudges each lease window until the PR is merged or closed.

For the full flow, see [decision-tree.md](./decision-tree.md).

---

### Local development

**Prerequisites:** [uv](https://github.com/astral-sh/uv)

```bash
# Install dependencies (including dev tools: ruff, ty, vulture, pip-audit, pre-commit)
uv sync --dev

# Copy and fill in your env vars
cp .env.shadow .env
```

Edit `.env` and set at minimum:

```
GH_TOKEN=ghp_...          # GitHub personal access token (repo scope)
SLACK_BOT_TOKEN=xoxb-...  # Optional: enables Slack DMs to PR authors
```

**Run the script:**

```bash
uv run python -m stale_pr_review
```

**Run tests:**

```bash
GH_TOKEN=dummy uv run pytest tests/ -v
```

**Lint, format, type-check, dead-code, and audit:**

```bash
uv run ruff check --fix .       # lint (incl. bandit-style `S` security rules)
uv run ruff format .            # format
uv run ty check stale_pr_review/   # static type check
uv run vulture stale_pr_review     # dead-code detection
```

**Run all quality gates at once via pre-commit:**

```bash
uv run pre-commit run --all-files                        # lint/format/type/dead-code/audit
uv run pre-commit run --all-files --hook-stage pre-push  # adds the test suite
```

**Install pre-commit hooks** (one-time setup; installs both the pre-commit gates and the pre-push test run):

```bash
uv run pre-commit install
```

**Run the script with docker**

```bash
docker build -t stale-pr-review .
docker run --rm --env-file .env stale-pr-review
```

---

### Testing safely

Use these modes to test without sending real comments or Slack messages:

**Dry run** — prints all actions, sends nothing:

```bash
DRY_RUN=true uv run python -m stale_pr_review
```

**Target a single PR** — useful when you own the PR and want to see exactly what would happen:

```bash
TEST_PR_NUMBER=1234 uv run python -m stale_pr_review
```

**Both together** — safest option:

```bash
DRY_RUN=true TEST_PR_NUMBER=1234 uv run python -m stale_pr_review
```

`TEST_PR_NUMBER` bypasses staleness and comment-lease checks so the PR is always processed, regardless of its last-updated timestamp.

---

### Environment variables

| Variable | Required | Default | Description |
|---|---|---|---|
| `GH_TOKEN` / `GITHUB_TOKEN` | yes | — | GitHub API token |
| `SLACK_BOT_TOKEN` | no | — | Slack bot token for DMs to PR authors |
| `ORG_MEMBERS_B64GZ` | no | — | Base64-gzipped JSON of org members for Slack email lookup |
| `STALE_DAYS` | no | `7` | Days without activity before a PR is considered stale |
| `DRY_RUN` | no | `false` | Print actions without executing them |
| `TEST_PR_NUMBER` | no | — | Process only this PR, bypassing staleness and lease checks |

### Slack app

To enable Slack DMs, create a Slack app with the `chat:write`, `im:write`, `search:read:users`, `users:read`, `users:read:email` scopes and install it to your workspace. Then set the `SLACK_BOT_TOKEN` env var to the bot token provided by Slack.
