Skip to content
Back to skills

Pull Requests

ASecurity

Procedures for a pull request. Load it before opening a PR or editing its body, before writing test steps for a change under review in a PR body or in chat, before reviewing or merging a PR, before capturing or uploading screenshots or recordings for one, and before opening a GitHub issue. It covers the body layout, the prompts block, the template answers, How to verify, manual steps, the review page and its reply, the merge, the pictures and the Chrome preview of a new issue.

  • 3 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added November 6, 2026
developmentjavascriptpythongojavashellgitapidocumentation

Works with

  • claude code
  • terminal
  • cli
  • api

Security analysis

A100/100

Pro scans all 4 files and shows the line behind each finding

Scanned November 11, 2026

npx -y skills add zululima/dotclaude --skill pull-requests --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Pull Requests?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Pull Requests
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/zululima-pull-requests/badge)](https://www.skillsdirectory.com/skills/zululima-pull-requests)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: pull-requests
description: Procedures for a pull request. Load it before opening a PR or editing its body, before writing test steps for a change under review in a PR body or in chat, before reviewing or merging a PR, before capturing or uploading screenshots or recordings for one, and before opening a GitHub issue. It covers the body layout, the prompts block, the template answers, How to verify, manual steps, the review page and its reply, the merge, the pictures and the Chrome preview of a new issue.
---

# Pull requests

These procedures sit beside the Pull requests rules in `~/.claude/CLAUDE.md`, which still hold:
drafts only, one draft PR per issue and every other change on one already open, and never post on
GitHub.

## Opening

- Public and upstream repositories: never name a downstream project, its client or its customer. Say
  "a downstream project". Redact them in quoted prompts with square brackets, and redact absolute
  paths and container names that carry them.
- In a fork checkout, resolve the GitHub handle at runtime with `gh api user --jq .login` (never
  hardcode it) and open the PR with `--repo <upstream-owner>/<repo> --head <handle>:<branch> --base
  <default branch>`. In a direct checkout, open it with `--head <branch>`.
- Upstream PRs only when asked. First check what is already open with `gh pr list --repo
  <upstream-owner>/<repo> --author @me` and prefer adding to an open PR.
- Never add `Co-Authored-By: Claude`, "Generated with Claude Code", or any Claude or Anthropic
  attribution to commits, PR bodies, or issues. The `attribution` setting also enforces this.

## The body

- Claude's part of the body is, in this order: the one sentence on what has changed, a list of the
  changes, the line that closes issues (`Closes #n`, with any related documents), the two blocks
  below, then the rest of the repository's pull request template filled in as described further
  down. Nothing else: no free-form summary or test plan outside that. The sentence, the list and the
  closing line stand on their own, without a heading, even where the template gives them sections of
  their own. Without a template the body ends after the blocks and the user writes the description.
- The list of changes under the sentence is built from the PR's final diff against its base, from
  the merge base to the head; the commits only help to understand intent. One short bullet per
  meaningful change, or per group of related ones, saying what it does and, when that helps the
  reviewer, why. No file names, paths or line numbers, and no detail the diff already shows unless
  it explains a behaviour or a decision. Tests, translations and documentation stay out unless they
  are a purpose of the PR. Uncommitted changes are left out. The list is rebuilt with the sentence
  whenever a push changes what the PR does.
- The closing line names the parent of a closed issue too when this PR closes the last of its
  sub-issues still open, since GitHub leaves a parent open after its last sub-issue closes. The
  issue's `parent` and that parent's `subIssues` with their states come from `gh api graphql`, and
  the check runs again whenever the closing line is written or the body refreshed, as a sub-issue
  may close elsewhere in the meantime. A parent whose body names work that none of its sub-issues
  covers stays off the line, and the reply says why.
- When a PR, draft or not, closes an issue, assign the user to that issue, with `gh issue edit <n>
  --add-assignee <handle>` and the handle from `gh api user --jq .login`. Do it when the PR is
  opened and again whenever its `Closes` list changes, and skip the issue silently when the edit is
  refused for lack of permission. The board then shows who holds the work without a separate step.
- The two blocks come straight after the closing line and above the rest of the template. They
  are a TL;DR for the reviewer.
- First block: a fenced code block holding the URL of the public configuration repository
  described under Public configuration repository in `~/.claude/CLAUDE.md`, pinned to a commit
  as `<repository URL>/tree/<full hash>`, so teammates can read and copy the rules the body was
  last checked against, not ones changed since. Take the repository URL at runtime from the
  checkout's `origin` remote, with `git -C ~/Projects/dotclaude remote get-url origin`,
  rewritten from the SSH form to https and without the `.git` suffix, and the hash from
  `git -C ~/Projects/dotclaude ls-remote origin HEAD`, the commit GitHub serves as the default
  branch, never from memory or from a local commit not pushed yet. Every edit of the body takes
  the hash again, and every push to that repository edits each open PR that carries the block,
  as Public configuration repository in `~/.claude/CLAUDE.md` says, so the block names the
  commit current at the body's last edit. A code block and not a Markdown link: a link's
  visible text can differ from where it goes, while a URL the reader pastes is one they see in
  the address bar before they go.
- Second block: `<details><summary>Prompts behind this PR</summary>`. Its first line names the
  model and effort level the session ran with, for example `Model: claude-fable-5-1, effort:
  medium`. Take them, and the prompts under it, as the bullet on printing the prompts in the
  `commit-messages` skill says, never from memory. Then number every prompt from the session in
  order and quote each verbatim, typos and lowercase included, as a `>` blockquote (`**1.**` on its
  own line, then the `>` quote). Include prompts that produced no commit. Mark answers to questions
  with a short parenthetical after the number, such as `**2.** (answer to: shall I build it?)`.
- Add a prompt about the change under review to the PR's prompts block in the same turn it is
  answered, whether or not anything is committed. Refresh the prompts block again after every commit
  and push.
- Directly under the model line, before the first prompt, a nested
  `<details><summary>Abbreviations</summary>` block lists every abbreviation used anywhere in the
  quoted prompts with its expansion, one per line, so a reader who does not know one can open
  the list and check. When the prompts use none, leave the block out.
- Pasted text inside a quoted prompt is replaced by a bracketed description, as the commit
  message rules say. The prompts may come before the rest of the body here, unlike in a commit.
- Leave out meta prompts: anything about Claude Code itself, its memory, `~/.claude/CLAUDE.md`, or
  how PR bodies are laid out in general, even when the same prompt also asks for a small fix to the
  PR. Only prompts about the change under review belong in the PR, and a prompt that adds to or
  corrects what this PR's body says or shows, such as a step, a picture or an upload, is one of
  them: the body edit that answers it adds it to the prompts block. A prompt mainly about the change
  keeps that part, and its meta part becomes a bracketed note such as `[a question about Claude Code
  settings left out]`.
- Below the blocks, fill in the rest of the repository's pull request template when it has one.
  Look in `.github/` for `pull_request_template.md` or `PULL_REQUEST_TEMPLATE.md`, a
  `PULL_REQUEST_TEMPLATE/` directory, and the same names at the repository root and under
  `docs/`. Keep the template's headings, comments and order, apart from the summary and the
  issue links, which go to the top as above, and answer each section briefly, such as the
  manual steps a reviewer follows to verify the change in the running app. A section
  that asks a yes/no question with a checkbox for each answer, such as "Added tests?" or "Is
  documentation up-to-date?", keeps only the box that applies, ticked, and the other box's
  line is deleted rather than left empty: nothing more when the answer is yes, one sentence
  saying why when it is no.
  `gh pr create` applies the template only when it composes the body interactively. `--body`
  and `--body-file` replace it silently, so the body file has to contain the template text.
- To change the body, read the live one into a file with `printf '%s' "$(gh pr view <n> --json body
  --jq .body)" > <file>`, edit it there, write it back with `gh pr edit <n> --body-file <file>`, and
  re-read to confirm. The command substitution drops the newline `--jq` prints after the body, which
  `--body-file` would keep as one more blank line on every edit, and any that earlier edits left.
  Outside the blocks, change only a template answer that a later commit made wrong or left
  incomplete, and keep every edit the user has made there.
- After a commit that answers review threads, read How to verify against each thread. A fix that
  changes what a reviewer sees or does gets a manual step, or a sentence in the step that already
  shows that screen, and the summary sentence and the list are rewritten when they no longer cover
  the PR. A fix nothing on screen shows needs no step.

## How to verify

- Test steps for a change under review, in a PR body's "How to verify" or given in chat, end
  with the last check. No step switches the checkout back to the branch it was on before: the
  next review's checkout replaces the branch anyway, and where to go next is the reader's call.
  Automated checks come first, the verify command and then end to end, and the manual pass comes
  last, unless the user says otherwise: a failing spec shows before the slow part, and end to end
  never follows the manual steps.
- The automated part is two command steps, each one fenced block joined with `&&`, since each
  is a sequence that should stop at its first failure. The first, run in a tab of its own,
  fetches, switches to the branch, updates the submodules and starts the stack. The second, in
  another tab, runs whatever the stack needs after a switch (a cache clear, a build), the verify
  command and the end to end run. Both run in a subshell that opens with `cd` into the checkout, as
  in `(cd ~/Projects/<repo> && git fetch --all --prune && ...)`, since the reader pastes them into a
  new terminal, which starts in `~`. The manual steps follow them.
- A session that works in a worktree of its own detaches it from the PR's branch, with `git switch
  --detach` there, before the reply that hands over the steps or the review page: git refuses to
  switch the reader's checkout to a branch another worktree holds, so the first step fails.
  `build.py` checks this and exits 1, naming the command, while a worktree still holds the branch.
- A command step that runs for minutes, such as the second, ends after its subshell with a
  notification that says how the run went and brings the reader's terminal to the front when
  clicked, so the reader can work elsewhere meanwhile: `(...) && m=passed || m=failed;
  terminal-notifier -title 'PR <n>' -message "verify and e2e $m" -activate com.apple.Terminal ||
  printf '\a'`. The `&&` and `||` let it fire on a failure too, the id is Terminal.app's unless the
  reader uses another terminal, and the bell stands in where `terminal-notifier` is missing.
- Manual steps follow the screen. Before writing one, open the component or the running page and
  take the order of fields and clicks from it, so a dialog that shows type, then name, then
  company is filled in that order and never in the order the step's purpose brings them to mind.
  Items a step names from a list on screen, such as the rows a search leaves, come in the order the
  screen lists them. Every control is named by the label the reader sees, in bold, in the locale the
  app renders (the catalog's `msgstr`, or the `msgid` when that is empty), never by what it does for
  the flow: click **Add item**, search for C and click its card, not "pick an item"; the row's
  **Delete**, not "remove it". A verb alone never stands for a control. When a label is on more than
  one control, say which: the row's, the dialog's, the bottom bar's. One action per sentence,
  followed by what the reader should now see, and two actions that each trigger the same thing get a
  sentence each, never "both ask too". Test data the reader creates gets the shortest name that
  works, a, b, c or 1, 2, 3, never a prefix such as the PR's number, unless a screenshot or
  recording of that step shows other data: then the step has the reader type and pick exactly what
  the picture shows, so the screen matches the picture. A step that searches by name says what to
  type and uses the shortest string the seed data does not contain, checked with a search in the
  grid first. A value the reader types that is hard to keep in mind, such as a number sequence, an
  ID or a long name, goes in a `text` block of its own under the sentence that types it, so the
  review page copies it with one key, while a short word such as a, herv or company1 stays inline.
  Every action the reader takes is written down, also the ones that feel obvious: copy the address
  before pasting it, open the new tab before pasting into it, log in again after logging out. A step
  never relies on something the reader has not been told to do. A step that opens a page gives its
  full local address, such as `http://localhost:3000/items`, in a `text` block of its own under the
  step, never the page's name alone and never inline, so the reader copies it in one click instead
  of hunting through the navigation. Steps run in the order their data needs, deletions last. A step
  that uses a seeded record says it comes from the seed and what to type to find it. Where the
  screen shows a stand-in, such as placeholder data, a mock or an unwired part, or a part that
  ignores the step's input, the step says so. A limitation found while building goes into its step,
  not only into the reply.
- The user works through the steps on the review page that Reviewing a PR describes, for every PR
  and not only in a review. The session that opens a PR, or pushes a commit that changes its steps
  or what they show, builds the page in the same turn, after the pictures are in the body, with the
  checks it ran as the overlay's results, and its reply ends with the page's address as a review's
  reply does.
- The review page is in Finnish, for the people who read it. The PR body, its steps and everything
  else an agent reads or works from stay English, since models work best in English: the session
  writes the steps in English as above, then once per build translates each step's title and text,
  and each finding and added step, into the overlay's `fi`, as `build.py` describes, keeping
  commands, addresses and values in their blocks as they are. The translation is only for the page:
  no session reads it or the page back, and a later build translates the English again.
- Where someone else reviews the repository's PRs, as its project memory or the user says, the user
  still checks the change by hand first, so the session builds the page and the reply ends with its
  address as for any other PR. Once the PR is done, the reply also asks whether the session marks it
  ready and requests that person's review.

## Reviewing a PR

- A check or review the user asks for on a PR whose author is the user, such as of its links or its
  pages, ends with the fixes the session would recommend committed and pushed to that PR's branch in
  the same turn, and the reply lists each fix with its commit, so a no costs one revert. A prompt
  that asks a question, for thoughts, a yes or no or a why, or says to change nothing yet, gets an
  answer only. A fix the request, the code or a source cannot settle, such as a choice between two
  layouts, is asked as under Running things in `~/.claude/CLAUDE.md`, and a step beyond the branch,
  such as a merge or a change outside the repository, stays a proposal. A review of a PR another
  author opened stays findings for that author, as this section says, and no session pushes to that
  author's branch, a fix it has made and tested included: the fix goes into the finding as the
  change the review would make, for the author to commit.
- A PR review switches the checkout to the PR's branch and leaves it there, runs the two command
  steps above itself, and checks the PR's manual steps against the screen. "Here" or "only" in a
  review prompt limits where the reply goes, never what runs.
- The steps the reader follows go on a review page, never into the reply, so they cannot drift from
  what the PR holds. Build it with `review-page/build.py` beside this skill, run in the checkout as
  `python3 -I ~/.claude/skills/pull-requests/review-page/build.py <owner/repo#n> --review
  <overlay.json>`. It writes the page to `.reviews/<repo>-<n>.html` in the checkout, a folder it
  adds to the checkout's `.git/info/exclude` so git never sees it, and it is a local file, never an
  Artifact. The script takes the steps of the PR body's How to verify? as GitHub holds them, embeds
  the PR's screenshots, downloaded with the `gh` token, and adds from the overlay the review's
  results, its findings and the steps it adds. The findings come first, as steps, so the reader can
  request changes before the manual pass. A finding fixed during the review carries `fixed` with its
  commit and stays off the page, since the reader has nothing left to do about it; the reply names
  the fix. A result's name says in plain words what ran, such as manual steps replayed by a script,
  and its detail is a few words, what ran on which head with what count, such as 119 of 119 on
  abc1234, and the script refuses a longer one. The browser tab reads Review PR and the number, with
  a tick for an icon. Checks that failed or did not run and the count of open findings go in a box
  above the steps, and a review where all is well shows none. The page numbers its steps from 1
  across all sections and opens on the first step not done, so its keys work at once. `j` and `k`
  move to the next or the previous step and scroll it to the top of the window, alone on the screen
  with nothing of the steps before or after it in view, and a section's first step brings the
  section's heading and note along. `x` ticks it and moves on to the next one, or clears a tick and
  stays, `c` copies its first command or address and `o` opens its screenshot. The current step
  lists these keys under its title and its first block says that `c` copies it, so the page has no
  key list of its own. On a wide window the steps sit centred, with the PR and the progress in a
  rail on the left that stays in view while the steps scroll; a narrow one keeps only a thin
  progress bar at the top. An address in a `text` block is a link on the page. The ticks live in the
  browser's storage for that file, so a later review of the same PR rebuilds the page at the same
  path and they stay; the session cannot read them, so the reader says how far they got.
- The reply gives the results of the verify command and the end to end run, then the findings and
  the fixes made, and no step list of its own. It ends with the page's `file://` address, with the
  full path from the root, in a `text` block of its own, which the user copies into Firefox's
  address bar, as `~/.claude/CLAUDE.md` says for every local page. The block is the last thing
  before Prompt feedback, where the reader's eye is when they go to copy it.
- Where the PR's steps fall short, the overlay's `added_steps` put the fix on the page, marked as
  added by the review: the two command steps when the PR lacks them (an empty `after` puts a step
  before the PR's first), a step for a change the PR's steps skip, and the bug steps below. They
  take the standing form above, from the screen, and a step that runs a command carries it in a
  block under it. Open every picture of the PR at full size before checking its step against the
  screen.
- Before a PR is opened and in every review, check it against what people asked to change on the
  repository's PRs of the last 30 days, leaving out bots and Copilot's reviews. `gh pr list
  --state all --search 'updated:>=<date>' --json number,url,reviews` gives the review bodies, and
  `gh api --paginate 'repos/<owner>/<repo>/pulls/comments?since=<date>'` and the same with
  `issues/comments` give the inline and conversation comments. A correction counts wherever it
  applies to what the PR changes, also one made about another page, such as where a back link
  lands or how a field looks. Before opening, fix each miss on the branch. In a review each miss
  is a finding that quotes the comment and links it, and the overlay's `results` carry the check,
  with how many corrections applied and how many of those the PR follows. A correction about the How
  to verify section is left out of a review, as the last bullet says.
- Each finding carries its severity, high, medium or low. Every finding that is not low gets the
  shortest reproduction that shows it, so the reviewer can send the author back to fix it before
  reviewing anything by hand: from the stack running the PR's branch on its seed data, the fewest
  numbered steps that bring the bug out, then what happens, what should happen instead and, when the
  review found it, the code that causes it, as GitHub markdown in a fenced block under the finding,
  in the reply and in the overlay. The reproduction is run here as written before it goes in.
- Every bug the review finds has a step on the page where the reader sees it: an added step right
  after the step that reaches the state, opening with "Bug (finding n):", that gives the clicks that
  bring the bug out, then what the screen shows and what it should show instead. The finding's
  `step` names it, so the reader can go from either to the other. The bug's step undoes any change
  the later steps do not expect. A bug nothing on screen can show, such as a race or a path no click
  reaches, says so in its finding.
- A review says nothing about the PR's How to verify section, in the reply or on the page: no
  finding, no `missed` entry and no command steps or step text written for the PR's body. Where
  those steps fall short, the added steps above give the reader what they need without comment, and
  the reproductions stay the one thing in the reply written for the author.
- A reply that suggests the next PR review, such as once the PR before it is merged, suggests it in
  a session of its own, never as more work in this session: a `start_session` card with the whole
  brief, which the user starts with a click.

## Merging

- Delete the head branch of every PR a session merges, on GitHub and in the local checkout: `gh pr
  merge` gets `--delete-branch`. A local branch the merge leaves behind, such as when it ran with
  `--repo` outside the checkout, goes with `git branch -D` once the PR shows as merged, and the
  reply names its last hash, so `git branch <name> <hash>` brings it back.
- A check a repository runs before a merge on the branch with the default branch merged in, on a PR
  another author opened, runs on a local merge commit that is never pushed, since no session pushes
  to that author's branch. GitHub's squash merge lands the same tree when it reports no conflict.

## Screenshots and recordings

- Every manual step of How to verify gets a screenshot of the state it describes, in the PR body and
  so on the review page, whether or not the change alters what a page renders: the reader sees what
  should happen before acting. A recording comes on top where motion is the point, or where the
  change is visual and the repository asks for one, in its pull request template or in the agent
  instructions it keeps in context (`AGENTS.md`, `CLAUDE.md`). A repository may keep such asks out
  of the template so the body carries only what a reviewer reads, and they bind as much there.
- Capture them from the local stack on fixture data with a scratch Playwright script in the
  scratchpad, on the repository's Playwright install, never a config change in the repository:
  one PNG per manual step of "How to verify", in the state that step describes, named after the
  step, and a WebM only where motion is the point. A recording is made with `slowMo` of about
  400 ms on the browser launch and a pause of two or three seconds at every state a step
  describes, since a Playwright run at full speed is too fast to follow. Fixture data is what
  the project's own start command leaves, so capture on a stack that no end to end run has
  touched since its last clean start: specs add records the reader's stack lacks.
- Each PNG holds only the part of the screen its step is about, such as the open dialog or the list
  the step changes, taken with `locator.screenshot()` or a `clip`, so it stays legible at the size
  the review page gives it. The window around that part, a dimmed backdrop, empty margins and parts
  the step does not mention are left out. A picture already taken whole is cropped to that part with
  `sips -c` and `--cropOffset` before it is uploaded.
- Open every picture with Read before it is uploaded, and check that it shows the state its step
  describes and nothing a freshly started stack would not show.
- Whenever screenshots or recordings exist for a PR and show what it changes, upload them to it
  in the same turn they are made, without asking first: a reply never ends with a set waiting for a
  go. Upload them through GitHub's own editor in Chrome, where the user is signed in: drop the files
  into the PR's comment box, copy the markdown GitHub puts there, and clear the box without posting.
  Run `~/.claude/skills/pull-requests/pane-upload.sh -C <checkout> push
  <file>...`. It pushes the files on a throwaway branch and prints the JavaScript to run with
  Claude in Chrome's `javascript_tool` on the PR's page, which returns the markdown and clears the
  box, then `pane-upload.sh clean <branch>` deletes the branch. An error from the JavaScript is
  never worked around: a Chrome signed out of GitHub waits for the user to sign in, and text already
  in the box is the user's and stays.
- Each picture goes at the start of the step it shows, never on the line with the number: that
  line holds the number and one short clause naming what the picture shows, the picture follows
  on its own line under it, indented to the step, and the step's text comes after the picture,
  so the reader sees what to look for before acting. A recording goes the same way under the
  first step it covers, as a bare attachment URL on its own line, which GitHub renders as a
  player where a `[name](url)` link does not. Write the body back with `gh pr edit`.
- Redo the set after every push that changes what is on screen, the way the prompts block is
  refreshed, so no reviewer judges a stale picture, and rebuild the review page with it.

## Issues

- A GitHub issue the session opens, in any repository, is first filled in on that repository's
  new-issue page in Claude in Chrome: the title and every field of its form or template, with
  nothing submitted. The tab stays open on it, and the reply names that tab and the account the page
  is signed in as, read from the page. The issue is created only on the user's go after that, with
  the page's own submit button, so what goes out is what the user saw.

Files in this skill

  • SKILL.md23.8 KB
  • pane-upload.sh4.4 KB
  • review-page/build.py24.5 KB
  • review-page/template.html17.8 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

View all in development →

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.