# Pr

> Open a PR, wait for CI to pass, fix failures, address review comments, and loop until fully green. Use when opening a PR, fixing CI, or addressing review feedback.

- Skill: `drn/pr` (Agent Skill)
- Install (CLI): `npx skillmds@latest add drn/pr`
- Raw SKILL.md: https://api.skillmd.com/api/skills/drn/pr/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: drn (https://skillmd.com/u/drn)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/drn/pr

---


## Context

- Current branch: !`git branch --show-current`
- Git status: !`git status --short`

## Phase Protocol

This skill participates in a phase chain. Read `~/.claude/skills/_shared/resources/phase-protocol.md` for the full protocol.

**Before creating the PR:** Check `.context/phases/` for prior artifacts. If any exist, use them to enrich the PR description:
- `plan-*.md` or `think-*.md` → use the goal/summary for PR context
- `build-*.md` → use the changes summary and deviations
- `review-*.md` → reference review findings and their resolution
- `test-*.md` → include test results and coverage metrics

**After the PR is created and green:** Write a `ship-{ts}.md` artifact to `.context/phases/` (create with `mkdir -p .context/phases`). The **Detail** section should include the PR URL, CI status, and any fixes applied. The **Handoff** section should note the PR number for `/merge`.

**Do NOT `git add` or commit the `ship-{ts}.md` artifact (or any `.context/` file) into the PR.** It is agent-local scratch for cross-phase handoff, not reviewable content — a `ship-*.md` in the PR diff is a defect reviewers will flag. In every commit step below (Step 1 and Step 4b), stage only the real change; never use `git add -A` or `git add .` (they sweep in `.context/`). As a belt-and-suspenders safeguard, add `.context/` to the repo's `.gitignore` so the artifact can never be swept in.

## Your task

Open a PR for the current branch, then loop until CI is fully green and all review comments are addressed. Do not return until the PR is in a mergeable, green state.

**If a PR already exists for this branch, skip straight to Step 3** — even if there are no local changes or commits. Your job is to get the PR to a green, mergeable state, not just to open it. Never abort when a PR exists.

### Step 0: Check for existing PR

Determine the upstream repo slug (owner/name) — use `upstream` remote if it exists, otherwise `origin`. Use `gh pr list --repo <owner/repo> --head '<fork-owner>:<branch>' --state open --json number,url` to check if a PR already exists. If one exists, note the PR number and skip to Step 3.

### Step 1: Commit any uncommitted changes

If there are uncommitted changes (check git status), stage and commit them with an appropriate message before proceeding. **Do this before anything else** — uncommitted changes are work the user wants shipped. Stage specific files (`git add <path>`), not `git add -A`/`git add .`, so `.context/` scratch never gets swept in.

**Abort condition (only when NO PR exists):** After committing any uncommitted changes, if there are STILL no commits ahead of the default branch, stop and tell the user: "Nothing to ship — no PR exists and there are no commits on this branch."

### Step 2: Push the branch

#### 2a: Pre-push markdown formatting check

If the pending push touches any `.md` files, run `prettier --check` against them first **only when the repo actually enforces prettier**. Markdown table edits often force prettier to realign column widths in *other* tables in the same file, which trips `qlty fmt` in CI. Catching it before push avoids burning a CI cycle — but reformatting against a repo that doesn't enforce prettier can introduce regressions on files whose existing style differs from prettier's preferred output (e.g., joining `0.`-prefixed list items, swapping italics `*` ↔ `_`, adding blank lines after list intros).

Gate on a config that actually enforces prettier on markdown. A bare `.qlty/qlty.toml` is NOT enough — qlty only formats markdown when its config enables a prettier or markdown plugin, and reformatting a repo that never enforced prettier introduces noisy unrelated diffs. If no qualifying config is found, skip this step.

```bash
prettier_enforced=false
for cfg in .prettierrc .prettierrc.json .prettierrc.yml .prettierrc.yaml .prettierrc.js .prettierrc.cjs .prettierrc.toml prettier.config.js prettier.config.cjs; do
  [ -e "$cfg" ] && { prettier_enforced=true; break; }
done
# qlty counts only when its config actually enables a markdown formatter
if [ "$prettier_enforced" = false ] && [ -e .qlty/qlty.toml ] && grep -qE 'prettier|markdown' .qlty/qlty.toml; then
  prettier_enforced=true
fi
# Also detect prettier referenced directly from CI workflows
if [ "$prettier_enforced" = false ] && grep -rlE 'prettier' .github/workflows 2>/dev/null | grep -q .; then
  prettier_enforced=true
fi

if [ "$prettier_enforced" = true ]; then
  mapfile -t md_files < <(git diff --name-only "$base_branch"...HEAD | grep -E '\.md$')
  if [ ${#md_files[@]} -gt 0 ]; then
    if ! npx --yes prettier@3.3.3 --check "${md_files[@]}"; then
      echo ":: prettier reformatted markdown — running --write and re-staging"
      npx --yes prettier@3.3.3 --write "${md_files[@]}"
      git add "${md_files[@]}"
      git commit -m "Format markdown via prettier"
    fi
  fi
fi
```

The check is cheap (~2s for a typical PR) and catches the most common qlty fmt failure mode. Skip if no `.md` files are in the diff, if no prettier/qlty config is present, or if `npx`/`prettier` is not available — rather than failing the push or risking unrelated reformatting.

#### 2b: Push

Push the current branch to origin. Use `git push -u origin HEAD` if no upstream is set. If there is nothing new to push, that is fine — continue to Step 3.

### Step 3: Open a PR (or use existing one)

- If a PR already exists for this branch, use it. Print the PR URL on its own line as `PR: <url>` and skip to Step 4.
- Otherwise, create a PR following Steps 3a–3d below.

#### 3a: Analyze the diff

Determine the base ref (use `git branch -r` to find `origin/main` or `origin/master`) (e.g., `origin/master`). Run `git diff <base>...HEAD` and `git log <base>..HEAD --oneline` to understand what changed. Classify the change complexity:

- **Simple** (rename, typo, config change, dependency bump): skip to 3c.
- **Medium** (single-file logic change): do 3b lightly — read the changed file in full.
- **Complex** (bug fix across multiple components, deletions of non-obvious code, changes where the diff alone doesn't explain *why*): do 3b thoroughly.

#### 3b: Trace change context

For each modified file, read the full file (not just the diff hunk) to understand the surrounding code.

**For deleted or removed code:** This is the most important case — reviewers need to understand why the code shouldn't exist. For each deletion:
- Identify what the removed code was doing.
- Trace where else that method/class is called (use Grep) to find the other code path that now handles the responsibility.
- Read those files to confirm the duplication or explain why the deletion is safe.

**For bug fixes:** Trace the interaction between components that caused the bug. Read the files that call into or are called by the changed code. Identify the specific mechanism of failure (e.g., a race condition, a duplicated side effect, an incorrect assumption).

#### 3c: Gather conversation context

Scan the conversation history for context that should flow into the PR description:

- **Triggering links** — CI failure URLs, Jira tickets, Sentry errors, PagerDuty incidents that motivated the work.
- **Production impact data** — If the conversation contains query results or impact numbers, summarize them.
- **Root cause analysis** — If the conversation contains a diagnosis, capture the key insight.

#### 3d: Write the PR and create it

Use `gh pr create --repo <owner/repo> --base master --head <branch>`. Keep the title under 70 characters.

Scale the body to match complexity:

**Simple changes:** title + 1–2 bullet summary.

**Medium changes:**
```
## Summary
- [What changed and why]

## Test plan
- [How to verify]
```

**Complex changes (bug fixes, cross-file interactions, deletions):**
```
## Summary
- [What changed]

## Root cause
[Explain the interaction between components that caused the issue.
Include brief code references — e.g., "create_transaction.rb:34 already
calls CreateTransactionEvent, so the explicit call in reverse_transaction.rb
created a duplicate." Show the mechanism, not just the symptom.]

## Production impact
[Only if the conversation contains impact data — e.g., "1,208 duplicate
events in the last 30 days." Omit this section if no data is available.]

## Test plan
- [How to verify]

[Link to triggering CI failure, Jira ticket, or Sentry error if available]
```

Print the PR URL on its own line, formatted exactly as:

```
PR: <url>
```

### Step 4: Wait for CI and address feedback (loop)

Repeat the following until CI is fully green **and** there are no unresolved review comments:

#### 4a: Check CI status and unresolved threads

**First, probe mergeability before polling.** Run a one-shot:

```
gh pr view <number> --repo <owner/repo> --json mergeStateStatus,mergeable
```

- If `mergeStateStatus == "DIRTY"` or `mergeable == "CONFLICTING"`, the branch has merge conflicts with the base. GitHub Actions may delay or skip running CI for the PR, and even if it runs, the merge step won't succeed. **Do not enter the polling loop.** Fetch the base branch (`git fetch origin <base>`), rebase onto it (`git rebase origin/<base>`), resolve any conflicts, and force-push with lease (`git push --force-with-lease`). Then re-probe `mergeStateStatus` and continue once it is no longer DIRTY.
- Otherwise, continue to the polling loop below.

Then use `gh pr checks <number> --repo <owner/repo>` to see the current state of all checks.

- If checks are still running, poll `gh pr checks` with exponential backoff: wait 60s, then 120s, then 240s, capping at 300s between polls. Stop after 30 minutes total and report to the user.
- **Distinguish a genuinely-running slow check from a stuck/queued one** before either continuing to wait or declaring it stuck. A required check on a slow repo (a 10-minute build-test, for example) is easy to mistake for a wedged workflow. Probe the underlying check-run status for the head commit:

  ```
  gh api repos/<owner>/<repo>/commits/<headSha>/check-runs \
    --jq '.check_runs[] | select(.name|test("<check>";"i")) | {name, status, started_at, created_at}'
  ```

  Replace `<check>` with the exact check name from the `gh pr checks` output above — a typo in the filter returns "no run" even when the check is healthy, so confirm the name first.

  - `status: "in_progress"` with a real `started_at` = genuinely running. Keep waiting (continue the backoff) — this is normal for slow checks, not a stall.
  - `status: "queued"` with `created_at` more than ~3 minutes in the past, or no matching run at all (after confirming the name is right) = the check is stuck or was never scheduled. Surface this to the user rather than polling silently.

  This prevents both premature give-up on a slow-but-healthy check and a silent infinite wait on one that will never start.
- If `gh pr checks` reports "no checks reported" for 3+ minutes **and** `mergeStateStatus` is not DIRTY, the workflow may be misconfigured, paused, or require approval — surface this to the user rather than continuing to poll silently.
- If all checks pass, proceed to 4c to check for unresolved review threads. Only go to Step 5 when CI is green **and** 4c confirms zero unresolved threads.
- If any checks have failed, proceed to 4b immediately — do not wait.

#### 4b: If CI fails — diagnose and fix

- Use `gh pr checks <number> --repo <owner/repo>` to see which checks failed.
- For each failed check:
  - Use `mcp__github__get_job_logs` with `failed_only: true` and the workflow `run_id` to read the failure logs.
  - Analyze the failure. Read the relevant source files to understand the issue.
  - Fix the code. Make the minimal change needed to resolve the failure.
  - Stage and commit the fix with a clear message describing what was fixed and why. Stage specific files (`git add <path>`), not `git add -A`/`git add .`, so `.context/` scratch never gets swept in.
- Push all fixes with `git push`.

#### 4c: Check for and resolve ALL review threads

This step handles both human and automated bot comments (CodeRabbit, qlty, Copilot, GitHub Actions, etc.). Every unresolved thread blocks mergeability.

**Fetch unresolved threads** using `gh api graphql`:

```
gh api graphql -f query='
  query {
    repository(owner: "<owner>", name: "<repo>") {
      pullRequest(number: <number>) {
        reviewThreads(first: 100) {
          nodes {
            id
            isResolved
            comments(first: 10) {
              nodes {
                body
                author { login }
                path
                line
              }
            }
          }
        }
      }
    }
  }'
```

Filter to threads where `isResolved` is false. If there are zero unresolved threads, this step is done — return to 4a (which will proceed to Step 5 if CI is also green).

**For each unresolved thread, classify and respond:**

1. **Identify the author.** Check the first comment author login. Bot usernames typically end in `[bot]` (e.g., `coderabbitai[bot]`, `qltysh[bot]`, `github-actions[bot]`, `copilot-pull-request-review[bot]`).

2. **Triage the comment.** Read the comment body and the referenced code. Classify as one of:
   - **(a) Valid fix needed** — The comment identifies a real bug, missing error handling, or correctness issue. Make the code change.
   - **(b) Intentional design choice** — The comment suggests a change that conflicts with the existing pattern, project conventions, or was a deliberate decision. No code change needed.
   - **(c) Informational or stylistic** — The comment flags metrics (complexity scores, similar code), asks a question, or suggests a cosmetic change that is not worth addressing. No code change needed.

   For bot comments specifically: bot suggestions are frequently false positives or conflict with project conventions. Do not blindly apply every suggestion. Evaluate each on its merits. Complexity warnings from linter bots (qlty, CodeClimate) are almost always informational — the complexity is inherent to the function. Similar-code detection is usually informational when the code implements a shared interface pattern.

3. **Reply to the thread.** Always reply before resolving, so there is an audit trail:
   - **(a)** Reply confirming what you changed (e.g., "Fixed — added nil check at line 42").
   - **(b)** Reply explaining why no change was made (e.g., "This complexity is inherent to the dispatch logic — extracting sub-functions would hurt readability without reducing actual complexity").
   - **(c)** Reply with a brief acknowledgment (e.g., "Acknowledged — these implement the same abstract interface, so the similarity is expected").

   Use `mcp__github__create_pull_request_review_comment_reply` to reply to the thread, passing the first comment ID as `comment_id`.

4. **Resolve the thread** via GraphQL mutation:

```
gh api graphql -f query='
  mutation {
    resolveReviewThread(input: {threadId: "<thread_node_id>"}) {
      thread { isResolved }
    }
  }'
```

**After addressing all threads:** if any code changes were made, stage, commit, and push them before looping back.

**Expect AI re-reviewers to re-review on every push.** Tools like CodeRabbit and CI-based reviewers (a `claude-review`/`robothanx`-style job) re-run on each new commit, and each pushed fix frequently surfaces NEW incremental findings — often 2nd-order effects of the fix you just made (e.g., adding a call inside a signal handler prompts the next review to flag that call's missing timeout). This is normal: expect 2-4 cycles to converge, and do not treat a fresh finding after an otherwise-clean round as the loop being stuck. The "stuck (same failure 3+ times)" guard in Step 4d applies to the SAME finding recurring after you tried to fix it — genuinely-new findings each round are progress, not churn.

#### 4d: Loop back to 4a

After pushing fixes or addressing comments, loop back to wait for CI again. Continue until CI is fully green and no unresolved comments remain.

**Important:** If you are stuck in a loop (same failure 3+ times), stop and report the situation to the user rather than retrying the same fix.

### Step 5: Report success

When CI is green and all comments are addressed:

- Print the PR URL on its own line as `PR: <url>`.
- Summarize what happened: how many CI fix cycles, how many comments addressed.
- Confirm the PR is ready for merge.

