Create or Update a Pull Request
SkillCommunicationCreate OR update a pull request, a typed title (`fix:`, `feat:` … from `scripts/lib/pr-title.js`, which places the change in CHANGELOG.md), category labels chosen by judgment, a concise body from `.github/pull_request_template.md` that reads as the squash commit message, and one skill-owned "review notes" comment for the detail reviewers want but `git log` doesn't. Detects an existing open PR for the current branch and routes to update-mode (diff title/labels/body/comment, ask, then apply) instead of duplicating; aborts cleanly on merged/closed PRs. Scans branch + commits for related issues, falls back to `gh` issue search, surfaces architecture docs whose related-files were touched, and runs the four-pillar judgment passes (tech-debt scan, decision-shape detect, followup capture) so journal hygiene is part of shipping rather than a separate manual step. Required for all PRs in this repo, supersedes any default PR-creation flow.
Use Create or Update a Pull Request in Claude, ChatGPT or Ahel Desktop
Free. Sign in, add Create or Update a Pull Request and connect your AI. About a minute.
Also: Claude Code · Cursor · Codex
Then ask your AI: use the Create or Update a Pull Request skill
Details
Instructions available. Your AI can read the instructions. Execution depends on the setup they require.
Account requirements not reviewed. Check the skill instructions before use; ahel provides instructions and does not run this skill.
No other account needed.
Add ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.
What this skill tells your AI
The instructions your AI receives, as published by jellyrock/jellyrock in .claude/skills/pr/SKILL.md and read by ahel’s review.
Open a PR with a typed title, its category labels, a body from .github/pull_request_template.md filled from real signal on the branch, and a review-notes comment, and run the four-pillar judgment passes BEFORE pushing so journal hygiene lands in the same change set. If a PR already exists for the current branch, route to update-mode: diff what is there against a fresh render, ask before applying, and use gh pr edit instead of gh pr create. This replaces the generic PR-creation flow — do not call gh pr create or gh pr edit directly outside this skill.
Contract
Goal. Be the single, mandatory path for opening or updating a pull request in this repo. The skill titles the PR with the type that places it correctly in CHANGELOG.md, labels it for the GitHub UI, renders a body that reads well as the squash commit message, puts reviewer-only detail in one review-notes comment, and runs three judgment passes — tech-debt scan, decision-shape detect, followup capture — BEFORE the body is pushed, so journal hygiene lands in the same change set rather than as separate manual chores. It is create-or-update aware: an existing open PR for the branch routes to update-mode (diff the current title, labels, body and notes comment against a fresh render, confirm, then apply), and merged/closed PRs abort cleanly instead of opening a duplicate. It ships at the Sonnet tier because the work is template-fill + structured signal-gathering with bounded judgment, with the genuinely judgment-heavy tech-debt walk delegated to its own sub-agent — supersede any default PR-creation flow with this skill; never call gh pr create/gh pr edit directly outside it.
Inputs. No arguments — the skill operates on the current branch and its commits. It expects a non-main, non-detached branch with a clean working tree and an upstream it can push (the pre-flight establishes these, pushing the branch where needed). It reads the PR template, the title types in scripts/lib/pr-title.js, the branch's commit log and diff vs main, an existing PR's title, labels, body and review-notes comment (when one is open) including the <!-- /pr render: sha=... --> marker, and the architecture-docs related-files lint.
Outputs. A created or updated pull request with: a type: Imperative summary title (type from scripts/lib/pr-title.js, code identifiers backticked); one or more category labels; a body that is the filled template (hint comments dropped, optional sections present only when they have content — see "Build the body"); and one review-notes comment holding the verification detail, the docs and journals the PR touched, and the hidden <!-- /pr render: sha=<40-char> ts=<ISO-8601-UTC> --> marker that lets the next invocation narrow judgment-pass scope to "since last render". Also: drafted journal entries (tech-debt / decision / followup) surfaced per-candidate for the user to accept into /log; on the update path, a pre-render backup of the prior body and notes in .claude/handoffs/; and the PR URL printed. No journal entry is written without per-candidate user accept; nothing on the PR is overwritten without confirmation.
Success criteria.
- The pre-flight gates hold: not on
main, not detached, clean tree, branch pushed — hard failures stop and report; an obvious push is not gated behind a verbal question. - Existing-PR routing is correct: open → update-path (diff, confirm,
gh pr edit); merged or closed → abort with the right recovery instruction; none → create-path. - The four-pillar judgment passes run against the resolved
<lower>SHA (prior render marker on update,mainon create) so the user isn't re-asked about already-handled candidates, and each candidate is confirm/skip per-item. - The title passes
pr-body-check.js(a known type, a non-blank scope if any), backticks code identifiers, and names every user-visible change — it becomes the changelog line. Its type follows "Choosing the type" below, not habit. - Labels come from the deliverables the title and Overview name, each with a quoted phrase shown to the user; tooling-only work is
dev-improvement, anddocumentationgoes only on a docs-only PR (it makes journal-sync and the description check skip the PR). Labels the skill does not manage (merge-conflict,release-prep…) are never removed. - The body passes
pr-body-check.js --body-filebefore it is posted; related issues found are rendered asFixes/Ref #N, and an optional section with nothing to say is left out rather than written asNone. gh pr create/gh pr edit/Writepermission prompts are left intact — they are the user's gate on body content and backup creation, not suppressed.
Failure modes to avoid.
- Polluting the skill's context with the tech-debt walk. Never
Read docs/architecture/tech-debt.mdinline from inside/pr— that inline read IS the context pollution Pass 1 exists to prevent. Spawntech-debt-scanas aTasksub-agent with an explicitmodel: sonnet, and never narrate sub-agent invocation while the transcript shows aRead. - Auto-applying journal entries. The judgment passes produce drafts only; apply a tech-debt edit or invoke
/logonly on explicit per-candidate accept. - Overwriting a PR body without confirmation or backup. On the update path, always diff-then-confirm and write the prior body to
.claude/handoffs/beforegh pr edit; ifgh pr editfails, the backup is the recovery path — surface it, don't claim success. - Opening a duplicate on a merged/closed PR. Abort with the recovery instruction; never silently create a second PR.
- A title that names only part of the PR. The squash subject is the changelog line and cannot be edited after merge; re-derive the title from every user-visible change, on the update path too. The likeliest omission is a behavioral fix delivered inside a refactor — the exclusion for refactors is about diffs that change nothing a viewer can observe, not about fixes that happen to arrive as restructuring.
- A type picked by habit.
fix:on a change to the/prskill put "(skills) Make/prtitles…" in the user-facing Fixed section (#998): tooling changes take a hidden type. The reverse fails too — an untyped orchore:title on an app change drops it from the changelog or files it under Changed. - Labeling a PR's ingredients instead of its purposes. "Every label that honestly applies" gave #1029
code-cleanupfor a helper its fix needed and #1033dev-improvement+general-improvementfor the test hooks that proved a bug fix. Labels come from the deliverables the title and Overview name; tests, docs and supporting refactors earn none (see Labels). - Detail stuffed into the body. The body is the permanent commit message; measurement tables, device matrices and reviewer asides belong in the review-notes comment, with a one-line summary of the evidence left in
## Testing. - Rephrasing the title to drop a code reference when the spell-check precheck fails — backtick the identifier instead; dropping the reference is the wrong fix.
- Suppressing the create/edit/Write permission prompts by allowlisting them — they are intentional user gates.
When NOT to use.
- You want to investigate review comments on an existing PR — that's
/pr-review, not/pr(which CREATES or updates a PR). - There is no branch to ship (on
main, or nothing committed) — there's nothing to open a PR for. - You need to bypass the journal-hygiene passes for a genuinely trivial change — that's still in-scope (skip the judgment block with one confirmation), not a reason to call
gh pr createdirectly.
Implementation
gh pr create, gh pr edit, gh pr comment and Write are intentionally NOT pre-approved in this skill's frontmatter — the permission prompts they trigger are the user-approval gate for what gets posted and for local backup file creation. Don't try to suppress them. Editing the notes comment goes through gh api, which the project allowlists, so the skill's own apply/skip confirmation is that write's gate — never edit it without one.
The mechanical close-loop side (move ## Currently running → ## Recently shipped, bump last-updated:) runs automatically after the PR merges via .github/workflows/journal-sync.yml. This skill does NOT touch that — its job is the judgment side: tech-debt entries, decision entries, and followup entries that need a human call.
Pre-flight (abort if any fails)
Run in parallel:
git rev-parse --abbrev-ref HEAD— must NOT bemain, must NOT beHEAD(detached). If detached, abort and ask the user to check out a branch first.git status --porcelain— must be empty (no uncommitted changes).git rev-parse --abbrev-ref --symbolic-full-name @{u}— if no upstream, rungit push -u origin <branch>. The permission prompt is the gate; don't ask verbally.git rev-list --count @{u}..HEAD— if non-zero, rungit push.
If a hard check (on main / detached HEAD / dirty tree) fails, stop and report. Pushing a feature branch to open its PR is obvious — don't gate it behind a verbal question.
Detect existing PR (route create vs update)
Run gh pr view --json number,url,state,isDraft,author,title,labels,body,headRefOid for the current branch. Branch on state:
MERGED— abort. Print:PR #<N> is already merged at <url>. Switch off this branch (e.g.git switch main && git pull) before opening a follow-up PR.Don't try to update or open a duplicate.CLOSED(not merged) — abort. Print:PR #<N> at <url> was closed without merging. Reopen manually withgh pr reopenif you want to revive it, or start a new branch.Don't silently open a duplicate.OPEN— enter the update path. Capture<N>,<url>,<author.login>,<title>,<labels>,<body>, and<headRefOid>for later steps.- No PR exists (
gh pr viewexits non-zero with "no pull requests found for branch") — enter the create path (today's flow).
Update-path setup (skip on create path)
- Author warn (best-effort) — run
gh api user --jq .login. If the result differs from the captured<author.login>, print one line:Note: PR #<N> was opened by <other-user>. Body edits will appear under your account.Don't abort. Ifgh api userfails (auth/rate limit), skip the warn silently — it's informational only. - Find the review-notes comment —
gh pr view <N> --json comments --jq '.comments[] | select(.body | contains("<!-- /pr notes -->")) | {url, author: .author.login, body}'. Keep the last match authored by the current user as<notes>; its numeric id is the#issuecomment-<id>suffix of itsurl. None found (a PR opened before this skill posted notes, or by hand) → the create step for notes runs on apply. - Resolve lower-bound SHA — this becomes the input range for judgment passes (so the user isn't re-asked about candidates already accepted/skipped on the prior /pr render):
- Parse
<notes>for the marker<!-- /pr render: sha=([a-f0-9]{40}) ts=(\S+) -->; if there is none, parse the PR body (PRs rendered before the marker moved to the comment carry it there). If multiple markers exist (rare — copy-paste), take the LAST match. - If a marker SHA is found AND
git merge-base --is-ancestor <sha> HEADexits 0 → use that SHA. - Otherwise, fall back:
gh pr view --json commits --jq '.commits[0].oid'. If that SHA is also reachable from HEAD, use it. - Ultimate fallback (force-push edge case where neither prior SHA is reachable): use
main— same scope as the create path. Print one line so the user knows the narrow scope was lost:Note: prior /pr render SHA unreachable from HEAD (rebase or force-push?). Falling back to full-branch scope for judgment passes.
- Parse
- The resolved SHA is referenced as
<lower>throughout the rest of this skill. On the create path,<lower>ismain.
Four-pillar judgment passes (before drafting the PR body)
Three quick passes that surface journal entries the user should write — each with one-line confirm/skip per candidate. Drafts only; the user accepts before any /log invocation. Skip the whole block (with one user "skip judgment passes" confirmation) if the change is trivial (typo / dep bump / docs-only).
Both pass 1 and pass 2 use the <lower> SHA resolved in "Detect existing PR" above as their lower bound. On the create path that's main (today's behavior). On the update path it's the prior /pr render SHA — so the user isn't re-asked about candidates already accepted/skipped on a previous /pr invocation against the same PR.
Pass 1 — Tech-debt scan
Invoke /tech-debt-scan as a sub-agent (not inline) to keep its candidate-walk from polluting the /pr skill's context. Spawn it with an explicit model: sonnet (matching tech-debt-scan's own frontmatter pin) — a sub-agent with no model override inherits the session model, and if that session is a 1M-context model the spawn hits the "usage credits required for 1M context" gate and the pass fails. The tech-debt walk is a structured area-match + diff-propose task that sonnet handles correctly, so the explicit pin is the intended model, not a downgrade. Pass (substitute the resolved SHA for <lower>):
Read .claude/skills/tech-debt-scan/SKILL.md and follow the steps; scope the changed-files set to `git diff <lower>..HEAD --name-only` so only file areas that became relevant since the last /pr render are considered; surface candidate slugs + ask about new debt but do NOT apply edits — return the proposed diff for the parent to confirm.
Anti-pattern: do not narrate "running the tech-debt scan sub-agent" while reading docs/architecture/tech-debt.md inline via Read. That inline read IS the context-pollution this step is designed to prevent. If you find yourself about to call Read on tech-debt.md from inside /pr, stop and call Task with subagent_type: general-purpose AND model: sonnet instead. The sub-agent's job is to walk the entry list and return a diff; the parent's job is to surface that diff to the user. Never conflate the two — and never claim sub-agent invocation in narration when the JSONL will show a Read instead of a Task tool_use.
If the sub-agent returns proposed diffs (existing slugs to remove, new slugs to add), surface them to the user one at a time with apply / skip / edit per candidate. Apply via Edit only on user accept.
Pass 2 — Decision-shape detect
Run the existing nudge against the in-scope commit log (substitute the resolved SHA for <lower>):
node scripts/lint/decision-shape-nudge.cjs --range=<lower>..HEAD
If it surfaces matches, walk them with the user: "this commit message has decision-shape language — does it close off alternatives or have a non-obvious rationale worth recording?" If yes, invoke /log decision for that commit. If no (the keyword was incidental), move on. Don't draft entries for commits the user dismisses.
Pass 3 — Followup capture from PR body
While drafting the PR body's "Follow-ups" section (Step 4 below), if you find yourself writing a deferral that doesn't already have a tech-debt.md anchor, invoke /log followup for it (or /tech-debt-scan Step 4 if it's a refactor candidate that warrants a stable slug). Reference the new slug from the PR body.
The CLAUDE.md Followup-discipline rule governs which journal each deferral lands in. Follow it strictly — the rule's branching logic (/log followup vs /tech-debt-scan vs /log signal) is the answer, not the user's preference.
Gather context (in parallel)
git log main..HEAD --pretty=format:"%h %s%n%b%n---"— full commit history on the branch.git diff main...HEAD --stat— files changed summary.git diff main...HEAD --name-only— file list.node scripts/lint/check-touched-related-files.cjs --base main— architecture docs whoserelated-files:were touched.Read .github/pull_request_template.md— the template you'll fill.Read scripts/lib/pr-title.js—TITLE_TYPESis the list of types and the CHANGELOG.md section each one lands in. Read it rather than recalling it; it is the definition CI checks against.
Title
type: Imperative summary or type(scope): Imperative summary, under 70 characters. Synthesize from commits, not just the latest. Passed via --title, not in the body.
Choosing the type
The repo squash-merges, so the title is the first line of the commit on main, and scripts/changelog-syncer.js places the change in CHANGELOG.md by the title's type (TITLE_TYPES in scripts/lib/pr-title.js). A title with no known type fails CI (pr-body-check.js). Pick the type by what the PR does to the app a viewer runs:
- It changes the app (
components/,source/,locale/,images/,settings/,manifest) → a changelog type:featfor a new capability or setting,fixfor behavior that was wrong,updatefor behavior that changes on purpose,perffor the same behavior faster,refactorfor a restructure meant to change nothing (it is still listed — a refactor can regress, and developers read the changelog too),removefor a feature taken out,revertto undo a PR. - It only changes tooling around the app (
scripts/,.github/,.claude/,tests/,docs/, dev dependencies) → a hidden type:chore,ci,build,testordocs. A fix to a skill, a lint rule or a workflow ischore/ci, notfix:fix(skills):put "(skills) Make/prtitles…" in the user-facing Fixed section (#998). - Both → type it by the app change; the tooling rides along in the body.
A scope is optional. When the PR sits in one area, name it — fix(video):, chore(skills): — and keep it to that area's usual name; it appears in the changelog line as (video) …. Never write an empty or blank scope; CI rejects fix():.
Naming every user-visible change
The title is the changelog line — it must name every user-visible change in the PR. The changelog reads the PR's current title, so a wrong or incomplete title can be corrected after merge by editing the PR and re-syncing, but only until the release is cut: from then on the released section of CHANGELOG.md is fixed text, correctable only by hand. Get it right before merge. Before applying a title:
- List the user-visible changes from the Changes section you are rendering (what a viewer of the app would notice — not refactors, tests, or journal entries). A refactor that changes behavior is not a refactor for this purpose. Before excluding something as internal, ask what it makes the app do differently. If you can state it as "X used to sometimes fail, now it doesn't", it is user-visible and belongs in the title even though the diff reads as restructuring — and it belongs whether or not the old failure was reproduced, since the repo's own policy is to fix these races without a reproduction (ADR 0037). The tell is a commit subject that joins a fix to a restructure with "and": each half needs its own check against the title. Recorded 2026-09-22 — PR #1010's first title named only its teardown fix and dropped the "a subtitle track switch is no longer silently lost" fix, because the component split that carried it classified as a refactor and the list above says to exclude those.
- Check the title names each of them. When they don't all fit in 70 chars, name the outcome that covers all of them rather than the biggest one alone: two features joined by "and" is fine; dropping one is not.
- On the update path, re-derive the title from the full PR, never keep the old one by default. A PR that grew during review is exactly when a title goes stale: #996 was titled for its logo fit, gained a credits row in review, merged with the old title, and shipped a changelog line that omits the row.
Backtick every code identifier in the title — class/component/file names like `GridItem`, `BaseGridView.bs`, `ItemDetails`. The post-merge journal-sync writes the title verbatim into docs/progress.md and the PR-time precheck (journal-sync-precheck.yml) spell-checks it; a bare identifier fails that check. Synthesizing from commit subjects (which don't backtick) yields a bare title, so add the backticks yourself. When the precheck fails, backtick the identifier — never rephrase the title to drop the reference (that's the wrong fix, even though the old error message led with it). Backticks render as code in progress.md; GitHub shows them literally in the title, which is the accepted trade-off.
Labels
Labels are for finding PRs in the GitHub list — a person filtering by bug-fix wants the PRs that fixed app bugs. Nothing automated reads the category labels below: CHANGELOG.md places a PR by its title type, and journal-sync skips only on dependencies / documentation / docs-only / ci / automated / chore-only. So a label is right when someone filtering by it would want this PR, and wrong otherwise.
Pick them AFTER the body is final, from what the PR says it is for:
- Read the title and the Overview. Those name the PR's purposes. A deliverable is something they present as what the PR does — a clause of the title, an "and …", an "It also …". Something they present only as how or why another deliverable works ("rewrites the engine … because a swap alone would have shipped three defects") is part of that deliverable, not one of its own. A change that appears only as a Changes bullet — a small unrelated extra, a refactor the fix needed, the tests or docs that prove or explain a deliverable — is not a deliverable and earns no label, however real it is.
- Label each deliverable by what it does, with the one best label from the table. Several deliverables can share a label; a PR with several different kinds of deliverable gets several labels.
- Add
accessibilityalongside the main label whenever a deliverable changes what screen-reader, audio-guide or caption users get — even when the title or Overview says so in a single clause of a larger deliverable. It is a tag on a deliverable, never a deliverable of its own, so it needs no purpose of its own to qualify. - Tooling is not the app.
new-feature,new-setting,bug-fix,general-improvementandcode-cleanupdescribe the app a viewer runs (components/,source/,locale/,images/,settings/,manifest). A deliverable that changes only tooling — scripts, CI, tests and test harness, skills, agent config, developer docs — isdev-improvement, whether it fixes, adds or tidies something. A PR that changes only tooling isdev-improvementalone. - Name the evidence. Beside each label, quote the title or Overview phrase that earned it. A label you cannot quote a phrase for is dropped.
Shortened here. Read the whole file on GitHub.
Signals
- GitHub stars
- 45
- Forks
- 2
- Last commit
- Oct 2026
Advanced
- Item type
- skill
- Key
pr-jellyrock- Source
- github.com/jellyrock/jellyrock
github.com/jellyrock/jellyrock
More in Communication
Skill · anthropics
More in Communicationerror-handling
Skill · affaan-m
More in Communicationemails
Skill · coreyhaines31
More in Communicationwait-what
Skill · mattpocock
More in Communicationcold-email
Skill · coreyhaines31
More in Communicationazure-messaging
Skill · microsoft
More in Communication