open-pr · git:20261030.e6e926c · 2026-10-30 · sha256 337617dd569747a9

open-pr git:20261030.e6e926cA

Immutable. This exact content never changes and is served at /api/v1/blob/337617dd569747a9.

---
name: open-pr
description: Open a PR or an issue. Use before `gh pr create` or `gh issue create`, or before editing a PR's title or description.
metadata:
  internal: true
---

# Opening PRs and issues

- [Filing issues in this repo](#filing-issues-in-this-repo)
- [PR creation](#pr-creation)
- [Creation allowance](#creation-allowance)
- [Atomic PRs](#atomic-prs)
- [Keeping PR titles and descriptions current](#keeping-pr-titles-and-descriptions-current)

## Required skills

- Load `/tend-ci-runner:post-to-github` before composing or editing any PR or issue body.
- Load `/tend-ci-runner:act-in-other-repos` if the target is another repository.

## Filing issues in this repo

Where `tend-triage` is enabled (the default), a new issue starts a worker.
Choose a direct PR when the repair is ready or keeping it here makes useful
progress. Delegate independent repairs through issues when that lets a broader
investigation or maintenance sweep progress in parallel. Give the worker the
evidence and diagnosis already established, and reuse an existing repair owner.
An issue also preserves work that needs a maintainer decision or verification
this session cannot perform.

This governs your own repo only; filing into another repo follows `/tend-ci-runner:act-in-other-repos`.

## PR creation

When asked to create a PR, use `gh pr create` directly. Compose its title and body per `/tend-ci-runner:post-to-github`.

Before creating a branch or PR, check for existing work:

```bash
gh pr list --state open --limit 200 --json number,title,headRefName --jq '.[] | "#\(.number) [\(.headRefName)]: \(.title)"'
git branch -r --list 'origin/fix/*'
```

Base every PR on the default branch; never stack one on an unmerged bot branch, which puts the same change through review once per link in the chain.

Write PR titles, issue titles, and commit subjects in plain, literal language that a reader can understand without the body. Name the concrete component and behavior changed while keeping any prefix the repository requires. Put the explanation in the body. The test: someone who has not read the diff can say what changed. Example: `Stop worker retries after cancellation`.

The titles that fail it read as figures rather than descriptions — a metaphor, a subject withheld for effect, a phrase that only lands once you already know the bug. Rewrite to the literal statement: `Press again for the tab the driver lost, not the one Chromium never made` → `Retry the click when the browser driver never reports the opened tab`.

Describe the current PR for a maintainer deciding whether to merge it. Follow **Reader-facing prose** in `/tend-ci-runner:run-tend` and synthesize across commits and review rounds. The opening paragraph carries the merge decision — what changed, why, and what verifies it — and whatever a reviewer needs beyond that follows it or sits in `<details>`.

If an existing PR addresses the same problem, work on that PR instead.

### Closing keywords

`Closes #N` / `Fixes #N` belong on a PR that settles the whole report. Where it settles part of one — a second cause the issue names, a cleanup the code cannot decide — write `Refs #N` and name what is left in the body, so merging leaves the remainder in the open queue for a later run to find. A body that needs a "what this leaves" paragraph is the signal.

GitHub acts on the keyword in the squashed commit message as well as in the PR body, so both carry the same choice.

### Dedup recheck immediately before `gh pr create`

A separate mention on a different issue/PR can trigger a concurrent run asking for the same fix. Those runs are not serialized — each has its own concurrency group — so both may read an empty `gh pr list` at session start and then each open their own PR minutes later, producing near-duplicates. A long workflow queue (`tend-mention` can wait hours) also lets a sibling run open *and merge* a PR before this run starts — already-merged duplicates need to be in scope too. Re-run the check **as the last step before `gh pr create`**, with `--state all` so closed and merged siblings show up:

```bash
BOT_LOGIN=$(gh api user --jq '.login')
gh pr list --state all --author "$BOT_LOGIN" --limit 200 --json number,title,state
```

When the trigger is an issue/PR comment, also search for sibling PRs that reference that issue number — a merged PR's title or body often cites the issue (`Fixes #123`, `#123` in title) even when the branch name diverged:

```bash
gh pr list --state all --search "author:$BOT_LOGIN <issue-number>" \
  --json number,title,state,mergedAt
```

Use title keywords, issue references, and changed files to find candidate siblings. Read their changes and discussion to determine what work remains per **Determine what remains** in `/tend-ci-runner:respond-on-thread`. Apply any prior rejection below when deciding whether to continue an existing PR or open the remaining fix.

A fix may have landed directly on the default branch while you worked. Fetch it immediately before creating the PR:

```bash
DEFAULT_BRANCH=$(gh repo view --json defaultBranchRef --jq '.defaultBranchRef.name')
git fetch origin "$DEFAULT_BRANCH"
git log --oneline "HEAD..origin/$DEFAULT_BRANCH"
```

Inspect overlapping commits and reproduce the problem on the fetched default branch. If it is fixed, don't create the PR; comment on the triggering thread when it needs a response.

### Fetch the prior rejection before re-deriving a fix

A change a maintainer already turned down leaves its verdict in two places the checks above don't fetch: the closed PR that carried it, and the comments on the issue tracking it. Search by the symbol or path the change would edit — a finding re-derived from the code has no issue number, and an attempt predating the tracking issue cites none either — then read the closed hits' diffs and discussion, and the issue bodies, not just their titles.

Search, don't scan. A recency-ordered listing ages a rejection out in bot-throughput time: at a few PRs a day, any `--limit` drops it within weeks, and raising the cap only moves that boundary. A symbol match stays small however many PRs have landed since.

```bash
BOT_LOGIN=$(gh api user --jq '.login')
# <symbol>: the function, file, or config key the change would edit
gh pr list --state all --search "author:$BOT_LOGIN <symbol>" --limit 100 --json number,title,state,closedAt
gh pr view <n> --json comments,reviews --jq '[.comments[].body, .reviews[].body]'
gh pr diff <n>   # what it proposed; the thread records only what was discussed
gh issue view <n> --json body,comments --jq '[.body, .comments[].body]'
```

What you find governs: a PR closed on the **code** leaves the fix available to redo, while one closed on the **approach** means the semantics are still an open maintainer question — add findings to that thread rather than opening a second implementation of it.

## Creation allowance

Tend's proxy limits new issues and PRs in this repository through one allowance. When it is exhausted, the refusal names when capacity returns and the tracker a maintainer can close to approve more. A refused creation leaves the session running: keep diagnosis, reviews, comments, existing branch repairs and issue closure moving. Record a refused creation and the remaining work on its existing issue or PR; a CI report still owes any cause whose issue could not be filed. Preserve a prepared fix on its branch when appropriate, and follow the ordinary recovery path in **Recover issue work** in `/tend-ci-runner:notifications`. When an exhausted allowance is the only remaining blocker, defer creation until refill or approval. Changing labels does not change the allowance.

## Atomic PRs

Split unrelated changes into separate PRs — one concern per PR. If one change could be reverted without affecting the other, they belong in separate PRs.

## Keeping PR titles and descriptions current

When review changes the approach, recompose the title and description around the current result rather than appending a history of the changes:

```bash
gh api repos/{owner}/{repo}/pulls/{number} -X PATCH \
  -f title="new title" -F body=@"$TMPDIR/updated-body.md"
```

**A description describes the whole PR, not the increment this run reviewed.** It presents the current result coherently; prior attempts and review rounds stay in the thread unless they remain relevant to the merge decision. Scope every behavior claim in it to the PR's merge base, not whatever range this run happened to diff:

```bash
gh pr diff <number>   # merge-base→head, whatever this session has checked out
```

On a long-lived branch those are different commits — nightly's rolling `tend/update-workflows` PR accumulates a release per run, so its head is several releases past its merge base — and a claim that is true of the last increment can be false of the PR. If you can only verify the increment, name the base the claim is against instead of writing it as a claim about the PR. Nothing downstream catches a wrong description: it never turns a check red, and each later run re-anchors one increment further out.