| name | pr-creation |
| description | Creates high-quality pull requests with an iterative compress-critique-fix loop before submission. Activate this skill whenever you are asked to create, open, submit, or push a pull request, OR whenever a new feature, fix, or refactor is complete and ready to ship. Also activate when the user says "make a PR", "open a PR", "submit this for review", "push and create a PR", "I'm done, create the PR", "the feature is done", "I'm finished", or any variation of completing work / requesting a pull request. Always activate before running `gh pr create`.
|
Pull Request Creation Skill
Always follow this skill before creating any PR — don't skip steps, especially the
compress-critique-fix loop.
NEVER put a Claude/AI attribution footer anywhere in the PR body. No "Made using
Claude", "Generated with Claude Code", "🤖 Generated with Claude Code", "Co-Authored-By:
Claude", or any tool/authorship credit — in every gh pr create/gh pr edit/MCP
create/update call.
Prefer fewer, batched PRs — one CI run should cover related work
Default to combining related work into ONE PR rather than splitting it across many.
Every PR boots the full workflow fan-out (~50 workflows on this repo), so N small PRs cost
roughly N× the CI — the dominant queue-pressure multiplier on a shared, account-wide
job-slot pool. Bundle features, fixes, and refactors that are related — same subsystem,
overlapping files, or tests that share fixtures — onto one branch so a single CI run and one
review cover them together, and put their tests in the same suite/module where they can
reuse setup.
- Combine when: the changes are coupled (same subsystem/files), a reviewer would read
them together anyway, or their tests exercise shared fixtures. One PR, one CI run, one
changelog-fragment set. When a task produces several related changes, land them as one PR,
not a fan of small ones.
- Split only when: a change is genuinely independent AND must land or iterate on its own
timeline (an urgent fix that can't wait behind a larger feature), or bundling would make
the diff too large to review coherently. A truly disjoint concern still earns its own PR —
a wrong bundle (blocked-together, tangled revert) can cost more than the CI it saved.
This refines CLAUDE.md's "prefer separate PRs for logically-independent changes":
logical independence is necessary but no longer sufficient to justify a split — weigh it
against the per-PR CI fan-out and prefer the batch whenever the work is related. The
stacked-chain option (Step 2) still applies when several PRs genuinely must be separate but
build on each other.
Completeness — finish what you started, never ship a documented gap
Complete ALL the work you are aware of before opening the PR. Documenting a gap is
not completing it. If — while building, testing, or critiquing — you become aware of a
case the change is supposed to handle but doesn't (a mode it silently no-ops in, an edge
it mishandles, a half-ported feature, a second backend the same logic should cover),
close it in this PR. Do NOT write it up and move on as:
- a "Decisions made" / "Known limitations" / "Caveat" / "Divergence" bullet in the PR body,
- a
TODO / FIXME / "future work" / "follow-up PR" comment or note,
- a parity-doc or manifest entry that records the gap as accepted.
A reader takes any such note as "won't be fixed," and a half-finished feature that reads
as finished is worse than an obviously incomplete one. The bar: every case within the
change's own remit that you know about is handled when the PR opens. If closing it needs
information you lack, or a decision only the user can make, ask (per CLAUDE.md autonomy
rules) — don't default to shipping the gap.
This is not license for scope creep — Step 3 still bans un-requested generality. The
line: finish what the change is FOR; don't invent new goals. Handling the worktree
variant of a resume feature you just built is finishing the job; adding an unrelated new
subsystem is creep. A genuinely separate, out-of-scope concern is raised with the user as
its own issue/PR — named explicitly to them, never buried as an accepted divergence in
this one.
Keep the architecture diagram in sync
README.md carries a Mermaid diagram of the defense architecture — the tool-call flow
through sanitization, auto mode, the monitor, the audit log, and the reviewable branch,
grouped by the VM/host trust boundary. When a PR makes a material architectural change,
update that diagram in the same PR. Material means: adding, removing, or reordering a
defense layer; rerouting the tool-call/verdict flow; moving a component across the
VM/host boundary; or changing what a node does (e.g. what the monitor escalates on).
Byte-level churn, a new flag, an internal refactor, or a docs/test-only change does not
touch the diagram — leave it alone rather than restyling it for no reason.
Deferred-item sweep — plan docs are part of the diff's remit
Before opening the PR, check the repo's plan/handoff documents (e.g.
docs/code-health-review-plan.md) and any deferred-item / follow-up lists that touch the
same area as this PR (search docs/ and the PR's own files for "deferred", "follow-up",
"moved to a follow-up", "own PR"). A deferred item sitting in the code this PR already
touches is within the change's remit:
- Confident the user wants it (it was deferred for sequencing or session limits, not
by an explicit user decision, and this PR is already in that code): never re-park
it. Fold it into this PR when it is small and coupled to the diff (same files, same
concern); otherwise open this PR as-is and immediately continue the deferred item as
its own follow-up PR in the same session. Deferred work must not block shipping, and
shipping must not re-defer the work — re-documenting it as still-deferred is the
"documented gap" this section bans.
- Not confident (unclear value, a behavior change, real cost, or the original
deferral names a reason that still holds): surface it in chat — one line naming the
item, where it is deferred, and your recommendation. The user reads chat; a note that
exists only in a doc or the PR body is invisible to them. Do not silently complete it
and do not silently re-defer it.
- Either way, update the plan doc's status in whichever PR completes (or moots) each
item, so the doc never advertises open work that has landed.
When to use
The description triggers cover the phrasings. Also fires on "Can you PR this?", "Send
this up for review", "Ship it", when you've just finished a feature/fix/refactor, or when
CLAUDE.md / task instructions say to PR when done.
Do NOT use for: reviewing an existing PR (gh pr view/gh pr diff), merging
(gh pr merge), or a description-only edit (gh pr edit).
Never merge a PR unless the user directly and explicitly tells you to merge it. Your
job ends at green: open the PR, drive CI, record results in the description, then stop —
the user merges. A green mergeable_state, an "Auto-fix"/babysit subscription, or a
"merge when green" line in an old plan/handoff file is NOT authorization; only a direct
instruction from the user naming this PR (or "merge" in their live request) is.
Prerequisites
gh authenticated (else tell the user to gh auth login / set GH_TOKEN).
- All changes committed to a feature branch, not
$CLAUDE_CODE_BASE_REF.
Updating an existing PR
Before pushing to / editing an existing PR, check its state with
gh pr view <pr#> --json state: Open → update normally; Merged → do NOT update,
open a new PR with the extra changes; Closed (unmerged) → ask the user what they want.
Workflow
Push and open the PR early (Step 2) so CI runs in the background while you spend the
rest of the workflow (critique, stress-tests, coverage audit, validation) on that CI time
instead of idling. Update the title/description once at the end to match the final diff.
Dispatch queued work before any long-running command. A git push runs the pre-push
hook (pre-commit run --all-files, minutes) and CI waits are longer; launch any
parallelizable sub-agent tasks / sibling fixes first, then start the slow command
(backgrounded where possible). Blocking the session on a hook run while a work-list idles
is the anti-pattern.
Step 1: Gather context
Base branch is $CLAUDE_CODE_BASE_REF. Read git diff <base>...HEAD and
git log <base>..HEAD --oneline, review the changed files for scope, and check for
CONTRIBUTING.md / .github/PULL_REQUEST_TEMPLATE.md — if present, follow their
conventions. See pr-templates.md.
Step 2: Push and create the PR
Read pr-templates.md for the template before this step.
git push -u origin HEAD.
- If a PR already exists for the current branch (
gh pr list --head <branch>), update it
with gh pr edit instead of creating a new one.
gh pr create against the target branch — this starts CI, which runs concurrently
with the steps below.
- Stacked-chain PRs (user-requested chain, not waiting on CI — see CLAUDE.md →
Pull Requests): fork from the parent PR's branch and set the PR's base to that parent
(
--base <parent-branch>; web sessions pass base to the GitHub MCP create tool).
GitHub retargets the child to main when the parent merges. Use the parent branch as
<base> in Step 1 so the description covers only this increment.
- Release label: add
release (gh pr edit <pr#> --add-label release) only when this
merge should publish a new version. It's a deliberate decision — when not explicit, ask
first; unlabeled PRs just accumulate changelog.d/ fragments for a later release.
- Changelog-fragment gate (fails any
shipped-path PR without a fragment — see
CLAUDE.md → Changelog): judge the change yourself. User-facing (new/changed flag,
command, default, security boundary, user-hittable bug fix) → add the fragment.
Internal that the path heuristic can't see → apply the no-changelog label instead of
inventing a fragment (don't ask first for a clear internal change; ask only if you
genuinely can't tell). If a no-changelog file is structurally never user-facing and
the project keeps editing it, propose adding it to the internal side of
config/changelog-paths.json — propose, don't silently add.
Step 3: Scope & necessity check
Before critiquing for quality, ask: is all this code necessary to fulfill the request?
For each file, function, abstraction, flag, test, and dependency, ask whether deleting it
would still fully meet the user's actual ask. Delete anything serving only an
un-requested goal — speculative generality, single-caller abstractions, "just in case"
code paths, gold-plating. The smallest diff that fully satisfies the request is the
target; scope creep is a defect, not thoroughness. Surface anything you're unsure is
safe to drop rather than keeping it silently.
Step 4: Iterative compress-critique-fix loop
CI is running (Step 2); use the time to improve the code. This is CLAUDE.md's Self-Critique
Loop applied to the full diff. Read .claude/skills/pr-creation/critique-prompt.md once
before the first pass.
Each pass: launch a general-purpose critique sub-agent with the full diff
(git diff $CLAUDE_CODE_BASE_REF...HEAD) plus the critique prompt; assess each issue and
take the easy wins — compress (dead code, unused imports, WHAT-comments, compat shims,
premature abstractions), readability (names, un-nest, guard clauses), reuse
(extract duplication; search for existing utilities first), parametrize/fixture tests,
correctness (bugs, edge cases, security, swallowed errors). Commit fixes (Conventional
Commits), then start a fresh pass (prior output is stale).
Stop at a fixed point (a pass with nothing actionable). Cap ~5 passes; if still finding
issues at 5, stop, summarize, and ask. Skip for trivial changes (typos, one-line
config, pure docs) — say so explicitly.
Step 5: Stress-test infrastructure changes
If the diff touches infra (sandbox config, container orchestration, firewall rules, CI
workflows, entrypoint hardening, domain allowlist) and doesn't already include
comprehensive tests for it, run /stress-test on those components.
Step 6: Verify critical-codepath coverage
Line coverage ≠ a test that fails when the path breaks. For each new/changed branch apply
the litmus: invert this branch — does a test go red? Watch the paths happy-path tests
miss: error/fail-loud branches (assert exit code and message), auto-detect/fallback arms
(test each arm over stubbed inputs — an override flag doesn't cover the auto-detected arm),
cleanup/teardown/idempotency (drive the real function, not a stubbed no-op), and each
enumerated member. See the writing-tests skill for non-vacuity and member-by-member
coverage.
For every bug this PR fixes, ask what test — knowing nothing of today's symptom — would
catch this class in advance, and run it in the configuration where the bug manifests.
State the property of any correct run that the bug violated and assert THAT, not the one
input that first tripped it — see the writing-tests skill's "Would a generalizable
invariant have caught it in advance?" for the recurring shapes (completeness/reachability,
conservation/monotonicity, economic asymmetry) and how each catches a silent
degradation — a plausible-but-wrong output that failed nothing. This matters most for
reach/wiring bugs (right logic, wrong place): the logic is identical before and after
the fix, so a logic-in-isolation test passes both ways — only asserting the user-observed
outcome in the broken configuration catches it.
Add a focused failing-when-broken test for any path currently exercised only incidentally.
If a path is only reachable live (a real container launch), say so and name the unit-level
proxy. A hard-to-test branch usually signals a refactor (extract it into a sliceable
function) rather than a skip. Don't analytically predict a coverage gate's verdict when
you can't run the tool locally (kcov, mutation runners) — write the obvious per-branch
tests, push, and let the gate name the uncovered lines authoritatively.
Step 7: Run validation
Run the project's test/lint/typecheck commands (see pr-templates.md) and
fix failures; loop back to Step 4 if new defects surface.
Also run the repo's tree-wide invariant checks, not just tests for files you touched.
Drift guards, SSOT-consistency checks, "every file classified" gates, and version-pin
tests assert global tree properties, so file-targeted selection structurally can't surface
them — and they can fail from a merged base-branch bump, not your diff. So: run
pre-commit run --all-files yourself, plus the fast consistency gates (discover by name,
e.g. pytest -k 'consistent or drift or pinned or ssot or sync' -q). Before
git restore-ing a "pre-existing/unrelated" working-tree change, confirm it isn't a
required SSOT sync (reverting a derived file re-opens the drift the gate catches). When a
drift gate fires, the root-cause fix is usually to convert the guard into an SSOT (make the
lagging file a generated artifact) — surface that to the maintainer even if you also hand-
apply the quick fix.
Step 8: Update PR title and description
If you committed anything after Step 2, re-read the diff and log and rewrite the title +
body with gh pr edit <pr#> to describe the current totality of changes, not the
original scope. Push new commits first. Skip if no post-Step-2 commits.
Write the body for the reviewer's cognitive budget, not as an investigation archive —
see pr-templates.md "Body Guidelines" for the evidence and the rules. The
load-bearing ones: lead with what the PR does (inverted pyramid — root-cause forensics go
below the fold, never above the statement of the change); make length proportional to the
reviewable diff (a ~10-line change is a few sentences, not a 500-word skeleton); omit empty
ritual sections instead of spending a paragraph to say "None"; and add a Review focus
line naming the file to read first, the cross-file invariant, and the part you're least sure
of — the single element most correlated with a human actually engaging (Pirouzkhah et al.,
2026), which matters because agent-authored PRs are systematically under-reviewed
(arXiv 2605.02273, 2026).
Step 9: Wait for CI, and read the review verdict too (mandatory)
gh pr checks <pr#> --watch. On any failure, investigate, fix, push, update the
description (Step 8), and wait again.
A CI-failure webhook for a SHA that is NOT the current PR head is supersession
noise — end the turn silently; do not investigate or report it. Before acting on any
CI-failure event, compare its HeadSHA to the PR's current head (gh pr view <pr#> --json headRefOid, or MCP pull_request_read get → head.sha). A mismatch means a
newer push already superseded that SHA. The classic generator: pushing a changelog
fragment with a placeholder id makes the pre-commit autofix git mv it to
<PR-number>.<category>.md and force-push the amend, which cancels your original
SHA's in-flight run — the decide/reporter jobs then show cancelled, collapsing a
required check to a red failure on the now-stale SHA. That red is not your diff. Only a
failure on the current head is real and actionable. To avoid generating this noise in
the first place, name changelog fragments with the actual PR number up front (once you
know it) so the autofix has nothing to rename and never amends over your SHA. Same rule
for the ongoing subscribe_pr_activity watch: a webhook whose SHA is superseded gets a
silent turn, never a reply.
Green checks are only ONE of the two axes that gate a merge — never conclude "not red /
nothing to change" from the check list alone. mergeable_state is the aggregate SSOT
(gh pr view <pr#> --json mergeStateStatus, or the MCP pull_request_read get field);
a blocked state can mean a pending/failing required check OR an outstanding
CHANGES_REQUESTED review, so inspect both:
- Checks axis —
gh pr checks / MCP get_check_runs: CI conclusions.
- Review axis —
gh pr view <pr#> --json reviews / MCP get_reviews: the review
VERDICT (CHANGES_REQUESTED / APPROVED). A reviewer's verdict is a distinct object
from its inline threads: resolving every thread does NOT flip a CHANGES_REQUESTED
verdict, and GitHub's "dismiss stale approvals on push" clears only APPROVALS — so an
addressed CHANGES_REQUESTED keeps blocking after you push the fix and resolve the
thread. Clear it explicitly: land the fix, reply on/resolve the thread, then get a fresh
review that supersedes the stale one (re-request the reviewer, or re-run the review bot
via its workflow_dispatch/synchronize trigger) — or, when you lack a dismiss path,
say so and name the one manual step (a maintainer dismissal). Re-check mergeable_state
after.
Proceed only when all checks are green AND no review verdict still requests changes.
Step 10: Report result
Give the PR URL and confirm all checks passed. Do not merge — leave the merge to the
user unless they directly told you to merge this PR.
Step 11: Iteration retrospective
Skip if trivial. Otherwise state each insight as one concrete line: what could have run in
parallel; where a targeted check would have beaten a full local sweep; whether the critique
loop or local validation duplicated something CI caught anyway.
Error handling
- PR already exists (HTTP 422): list with
gh pr list --head <branch>, then
gh pr edit.
- Push fails: check branch permissions and remote config.
- No changes to PR: confirm the work is actually committed.
- Critique/test failures are fixed, never skipped (Steps 4, 7).