# Forge Pr

> Write engaging PR titles and descriptions for any forge (GitHub today; Bitbucket planned). Use when creating or updating PRs. Leads with narrative paragraphs and reaches for lists, tables, and diagrams when content is genuinely structured.

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

---


# Forge PR Writing

Write PR descriptions that fellow devs actually want to read. The writing guidance below is forge-agnostic — only the `gh` commands in the "Updating existing PRs" section are GitHub-specific today. Bitbucket support is tracked in [srid/agency#10](https://github.com/srid/agency/issues/10).

## Anti-patterns (what LLMs typically produce)

- Flat bullet lists of every file changed
- Implementation-detail dumps ("added `foo` parameter to `bar` function")
- Generic titles like "Update configuration" or "Fix bug in module"
- "## Changes" / "## Testing" / "## Summary" boilerplate headers
- Restating the diff in English
- Wall of prose for content that's _actually_ a list, comparison, or flow — five features described in one sentence, two before/after fixes blended into a paragraph, an architecture seam explained in words instead of drawn

## What to write instead

**Title**: Short, specific, interesting. Convey _what changed from a user/dev perspective_, not which files were touched. Use imperative mood. Under 70 chars.

**Body**: Open with a paragraph. Structure:

1. **Opening paragraph** — What this PR does and _why_, in 2-3 sentences. Bold the key behavioral change. If there's a motivating problem, state it directly.

2. **Details** — paragraph(s), bullet list, table, or fenced diagram, whichever fits the shape of the content (see [Use structure when content is structured](#use-structure-when-content-is-structured) below). Only include if the approach is non-obvious, has trade-offs worth calling out, or has discrete moving parts that benefit from being shown rather than described. Use _italics_ for subtle points; keep it high-level.

3. **Anything notable** — Breaking changes, migration steps, or things reviewers should pay attention to. Only if applicable. Use `> blockquote` for callouts.

## Use structure when content is structured

Narrative is the right tool for the opening "what changed and why" paragraph and for asides — but the moment you find yourself writing _"three loaders"_, _"two refinements landed"_, or _"the data passes through X then Y then Z"_, that's structure asking to be made visible. A comma-separated list of five features in prose is harder to scan than five bullets; a table beats a paragraph that says "previously Foo did X, now it does Y, and Bar previously did Z, now it does W."

Reach for these when they earn their keep:

- **Bullet lists** — discrete features, keyboard shortcuts, refinements, anything that isn't a narrative
- **Tables** — before/after fixes, per-variant comparisons (e.g. loader → format → vendor), trade-off matrices
- **Fenced ASCII or mermaid diagrams** — data flow, architecture seams, anything with shape that's easier to draw than describe
- **`###` subheaders** — break long bodies into navigable sections (still avoid generic `## Summary` / `## Changes`)

The rule is _use structure when content is structured_, not _always add structure_. A small PR with one paragraph of motivation is fine; don't reach for a table just to look thorough.

## Style rules

- Write for a dev skimming their PR feed — they should get the gist in 5 seconds
- **Bold** the most important phrase in each paragraph
- _Italics_ for nuance, caveats, secondary points
- Bullet lists, tables, and fenced diagrams are encouraged when the content is genuinely structured (see [Use structure when content is structured](#use-structure-when-content-is-structured)). Don't list the diff file-by-file or restate prose as a bulleted dump.
- `###` subheaders are fine to break up long bodies; skip generic `## Summary` / `## Changes` headers
- No filler: "This PR...", "In this change...", "As part of..." — start with the substance
- Link to issues/discussions where relevant (`Closes #123`, `See #456`)
- If the PR is trivial (typo fix, version bump), a one-liner body is fine

## Attribution footer

End every PR body with a one-line italic footer naming:

- the **invoking workflow** (the slash-command that drove this PR — e.g. `/do`), linked to its source repo (`https://github.com/srid/agency`),
- the **agent** (the harness identity — e.g. `Claude Code`, `Codex`, `opencode`), and
- the **model** (the specific model id you are running on — e.g. `claude-opus-4-7`, `gpt-5-codex`).

Reviewers should be able to tell at a glance what produced the diff:

> _Generated by [`/do`](https://github.com/srid/agency) on Claude Code (model `claude-opus-4-7`)._

Use the values that are actually executing this run — do not guess or fabricate. If you genuinely cannot identify one of agent/model, write `unknown` rather than omitting the footer. If the PR is being written without an invoking workflow (forge-pr loaded directly, no `/`-command), drop the workflow phrase and lead with `_Generated by Claude Code (model `claude-opus-4-7`)._`. When updating an existing PR (see [Updating existing PRs](#updating-existing-prs)) and the current run's agent or model differs from what's already in the body, **edit the existing footer in place** rather than appending a second one.

## Try it locally

If the repo is a **GitHub** Nix flake and the PR branch contains a buildable output (package, NixOS config, etc.), include a "Try it locally" section at the end of the body. Use the GitHub owner/repo and branch name to construct the command, and put it in a **fenced `sh` code block** (not inline backticks) so GitHub renders a copy button and the command doesn't line-wrap awkwardly:

````
### Try it locally

```sh
nix run github:<owner>/<repo>/<branch>
```
````

Adjust the command as needed — `nix build` for non-runnable outputs, add `#<output>` if the default package isn't the relevant one. Omit this section entirely if the change isn't meaningfully testable via `nix run/build` (e.g., CI-only changes, documentation, non-Nix repos, or non-GitHub forges where flake refs would be awkward).

## Passing the body to `gh` safely

**MANDATORY**: Always pass `--body` to `gh pr create` / `gh pr edit` / `gh pr comment` via a **single-quoted heredoc** so backticks, `$`, and `!` survive unescaped. Double-quoted `--body "..."` triggers shell command substitution on backticks, and escaping them with `\`` produces literal backslashes in the rendered PR (breaking code fences — see [juspay/kolu#402](https://github.com/juspay/kolu/pull/402)).

```sh
gh pr create --draft --title "..." --body "$(cat <<'EOF'
...body with ```sh fenced blocks``` intact...
EOF
)"
```

The `'EOF'` (quoted delimiter) is load-bearing — it disables interpolation inside the heredoc. Never write backticks in the body as `\``.

## Updating existing PRs

When the user pushes further changes to an already-PR'd branch:

1. Check if the PR title/description still accurately reflects the full scope
2. If new commits meaningfully change what the PR does, update the title and/or body via the forge's edit command (`gh pr edit` on GitHub)
3. Don't rewrite from scratch — amend the existing description to cover new ground
4. Add a brief note about what changed if the scope expanded significantly

## Examples

Worked bad/good pairs — a typical LLM bullet-dump, a small change carried by
narrative, and a richer one where a diagram and a table earn their keep — are in
[`EXAMPLES.md`](EXAMPLES.md). Read them when you want a model to imitate.

