
Create Pr
- 15 installs
- Updated March 18, 2026
- cartridge-gg/agents
This is a copy of create-pr by cartridge-gg - installs and ranking accrue to the original listing.
Helps with ai & agent building tasks.
About
create-pr is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- create-pr
- AI & Agent Building
- AI-coding skill
Create Pr by the numbers
- 15 all-time installs (skills.sh)
- Data as of Jul 28, 2026 (Skillselion catalog sync)
npx skills add https://github.com/cartridge-gg/agents --skill create-prAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 15 |
|---|---|
| Last updated | March 18, 2026 |
| Repository | cartridge-gg/agents ↗ |
What it does
Helps with ai & agent building tasks.
Files
The user likes the state of the code.
There are $git status --porcelain | wc -l | tr -d ' ' uncommitted changes. The current branch is $git branch --show-current. The target branch is origin/main.
$git rev-parse --abbrev-ref @{upstream} 2>/dev/null && echo "Upstream branch exists." || echo "There is no upstream branch yet."
Existing PR: $gh pr view --json number,title,url --jq '"#\(.number): \(.title) - \(.url)"' 2>/dev/null || echo "None"
The user requested a PR.
Follow these exact steps:
Phase 1: Review the code
1. Review test coverage 2. Check for silent failures 3. Verify code comments are accurate 4. Review any new types 5. General code review
Phase 2: Create/Update PR
6. Run git diff to review uncommitted changes 7. Commit them. Follow any instructions the user gave you about writing commit messages. 8. Push to origin. 9. Use git diff origin/main... to review the full PR diff 10. Check if a PR already exists for this branch:
- If PR exists:
- Draft/update the description in a temp file (e.g.
/tmp/pr-body.txt). - Update the PR body using the non-deprecated script:
./.agents/skills/create-pr/scripts/pr-body-update.sh --file /tmp/pr-body.txt- Re-fetch the body with
gh pr view --json body --jq .bodyto confirm it changed. - If no PR exists: Use
gh pr create --base mainto create a new PR. Keep the title under 80 characters and the description under five sentences.
The PR description should summarize ALL commits in the PR, not just the latest changes.
Phase 3: Monitor CI and Address Issues
Note: Keep commands CI-safe and avoid interactive gh prompts. Ensure GH_TOKEN or GITHUB_TOKEN is set in CI.
11. Watch CI status and feedback using the polling script (instead of running gh in a loop):
- Run
./.agents/skills/create-pr/scripts/poll-pr.sh --triage-on-change --exit-when-green(polls every 30s for 10 mins). - If checks fail, use
gh pr checksorgh run listto find the failing run id, then: - Fetch the failed check logs using
gh run view <run-id> --log-failed - Analyze the failure and fix the issue
- Commit and push the fix
- Continue polling until all checks pass
12. Check for merge conflicts:
- Run
git fetch origin main && git merge origin/main - If conflicts exist, resolve them sensibly
- Commit the merge resolution and push
13. Use the polling script output to notice new reviews and comments (avoid direct polling via gh):
- If you need a full snapshot, run
./.agents/skills/create-pr/scripts/triage-pr.shonce. - If you need full context after the script reports a new item, fetch details once with
gh pr view --commentsorgh api .... - Address feedback:
- For bot reviews, read the review body and any inline comments carefully
- Address comments that are clearly actionable (bug fixes, typos, simple improvements)
- Skip comments that require design decisions or user input
- For addressed feedback, commit fixes with a message referencing the review/comment
Phase 4: Merge and Cleanup
14. Once CI passes and the PR is approved, ask the user if they want to merge the PR.
15. If the user confirms, merge the PR:
- Use
gh pr merge --squash --delete-branchto squash-merge and delete the remote branch
16. After successful merge, check if we're in a git worktree:
- Run:
[ "$(git rev-parse --git-common-dir)" != "$(git rev-parse --git-dir)" ] - If in a worktree: Use the ask user question tool (
request_user_input) to ask if they want to clean up the worktree. If yes, runwt remove --yes --forceto remove the worktree and local branch, then switch back to the main worktree. - If not in a worktree: Just switch back to main with
git checkout main && git pull
Completion
Report the final PR status to the user, including:
- PR URL
- CI status (passed/merged)
- Any unresolved review comments that need user attention
- Cleanup status (worktree removed or branch switched)
If any step fails in a way you cannot resolve, ask the user for help.
#!/usr/bin/env bash
set -euo pipefail
interval="${POLL_INTERVAL:-30}"
minutes="${POLL_MINUTES:-10}"
poll_once="${POLL_ONCE:-0}"
pr=""
repo=""
exit_when_green=0
triage_on_change=0
usage() {
cat <<'USAGE'
Usage: poll-pr.sh [--pr <number>] [--repo <owner/repo>] [--interval <seconds>] [--minutes <minutes>] [--exit-when-green] [--triage-on-change]
Polls PR checks, review comments, and conversation comments every 30s for 10 minutes by default.
Environment overrides: POLL_INTERVAL, POLL_MINUTES, POLL_ONCE=1.
USAGE
}
while [[ $# -gt 0 ]]; do
case "$1" in
--pr)
pr="$2"
shift 2
;;
--repo)
repo="$2"
shift 2
;;
--interval)
interval="$2"
shift 2
;;
--minutes)
minutes="$2"
shift 2
;;
--exit-when-green)
exit_when_green=1
shift 1
;;
--triage-on-change)
triage_on_change=1
shift 1
;;
-h|--help)
usage
exit 0
;;
*)
echo "Unknown arg: $1" >&2
usage >&2
exit 1
;;
esac
done
if [[ -z "$pr" ]]; then
pr="$(gh pr view --json number --jq .number 2>/dev/null || true)"
fi
if [[ -z "$pr" ]]; then
echo "Could not determine PR number. Use --pr <number>." >&2
exit 1
fi
if [[ -z "$repo" ]]; then
repo="$(gh repo view --json nameWithOwner --jq .nameWithOwner 2>/dev/null || true)"
fi
if [[ -z "$repo" ]]; then
echo "Could not determine repo. Use --repo owner/name." >&2
exit 1
fi
if ! [[ "$interval" =~ ^[0-9]+$ && "$minutes" =~ ^[0-9]+$ ]]; then
echo "interval and minutes must be integers." >&2
exit 1
fi
iterations=$(( (minutes * 60) / interval ))
if (( iterations < 1 )); then
iterations=1
fi
if [[ "$poll_once" == "1" ]]; then
iterations=1
fi
echo "Polling PR #$pr in $repo every ${interval}s for ${minutes}m (${iterations} iterations)."
last_issue_comment_id=""
last_review_comment_id=""
last_review_id=""
last_failed_signature=""
if ! gh auth status >/dev/null 2>&1; then
if [[ -z "${GITHUB_TOKEN:-}" && -z "${GH_TOKEN:-}" ]]; then
echo "Warning: gh auth not configured (set GH_TOKEN or GITHUB_TOKEN)."
fi
fi
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
print_new() {
local kind="$1"
local time="$2"
local user="$3"
local url="$4"
local body="$5"
echo "New $kind by @$user at $time"
if [[ -n "$body" ]]; then
echo " $body"
fi
if [[ -n "$url" ]]; then
echo " $url"
fi
}
for i in $(seq 1 "$iterations"); do
now=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
echo "[$now] Poll $i/$iterations"
changed=0
checks_counts=$(gh pr checks "$pr" --repo "$repo" --json status,conclusion --jq '
[length,
(map(select(.status != "COMPLETED")) | length),
(map(select(.conclusion != null and .conclusion != "SUCCESS")) | length),
(map(select(.conclusion == "SUCCESS")) | length)
] | @tsv
' 2>/dev/null || true)
if [[ -n "$checks_counts" ]]; then
IFS=$'\t' read -r total pending failed success <<< "$checks_counts"
echo "Checks: total=$total pending=$pending failed=$failed success=$success"
else
echo "Checks: unavailable"
fi
failed_checks=$(gh pr checks "$pr" --repo "$repo" --json name,conclusion,detailsUrl --jq '
[.[] | select(.conclusion != null and .conclusion != "SUCCESS") |
"\(.name) (\(.conclusion))\t\(.detailsUrl // \"\")"
] | .[]
' 2>/dev/null || true)
failed_signature=$(gh pr checks "$pr" --repo "$repo" --json name,conclusion --jq '
[.[] | select(.conclusion != null and .conclusion != "SUCCESS") |
"\(.name):\(.conclusion)"
] | sort | join("|")
' 2>/dev/null || true)
if [[ -n "$failed_checks" ]]; then
echo "Failed checks:"
while IFS=$'\t' read -r name url; do
if [[ -n "$name" ]]; then
if [[ -n "$url" ]]; then
echo " - $name $url"
else
echo " - $name"
fi
fi
done <<< "$failed_checks"
fi
if [[ -n "$failed_signature" && "$failed_signature" != "$last_failed_signature" ]]; then
last_failed_signature="$failed_signature"
changed=1
fi
issue_line=$(gh api "repos/$repo/issues/$pr/comments?per_page=100" --jq '
if length == 0 then "" else
(max_by(.created_at)) | "\(.id)\t\(.created_at)\t\(.user.login)\t\(.html_url)\t\(.body | gsub("\\n"; " ") | gsub("\\t"; " ") | .[0:200])"
end
' 2>/dev/null || true)
if [[ -n "$issue_line" ]]; then
IFS=$'\t' read -r issue_id issue_time issue_user issue_url issue_body <<< "$issue_line"
if [[ "$issue_id" != "$last_issue_comment_id" ]]; then
last_issue_comment_id="$issue_id"
print_new "conversation comment" "$issue_time" "$issue_user" "$issue_url" "$issue_body"
changed=1
fi
fi
review_comment_line=$(gh api "repos/$repo/pulls/$pr/comments?per_page=100" --jq '
if length == 0 then "" else
(max_by(.created_at)) | "\(.id)\t\(.created_at)\t\(.user.login)\t\(.html_url)\t\(.body | gsub("\\n"; " ") | gsub("\\t"; " ") | .[0:200])"
end
' 2>/dev/null || true)
if [[ -n "$review_comment_line" ]]; then
IFS=$'\t' read -r rc_id rc_time rc_user rc_url rc_body <<< "$review_comment_line"
if [[ "$rc_id" != "$last_review_comment_id" ]]; then
last_review_comment_id="$rc_id"
print_new "inline review comment" "$rc_time" "$rc_user" "$rc_url" "$rc_body"
changed=1
fi
fi
review_line=$(gh api "repos/$repo/pulls/$pr/reviews?per_page=100" --jq '
[ .[] | select(.submitted_at != null) ] |
if length == 0 then "" else
(max_by(.submitted_at)) | "\(.id)\t\(.submitted_at)\t\(.user.login)\t\(.html_url)\t\(.state)\t\(.body | gsub("\\n"; " ") | gsub("\\t"; " ") | .[0:200])"
end
' 2>/dev/null || true)
if [[ -n "$review_line" ]]; then
IFS=$'\t' read -r r_id r_time r_user r_url r_state r_body <<< "$review_line"
if [[ "$r_id" != "$last_review_id" ]]; then
last_review_id="$r_id"
print_new "review ($r_state)" "$r_time" "$r_user" "$r_url" "$r_body"
changed=1
fi
fi
if [[ "$triage_on_change" == "1" && "$changed" == "1" ]]; then
"$script_dir/triage-pr.sh" --pr "$pr" --repo "$repo" || true
fi
if [[ "$exit_when_green" == "1" && -n "${pending:-}" ]]; then
if (( pending == 0 && failed == 0 && total > 0 )); then
echo "Checks green; exiting early."
break
fi
fi
if (( i < iterations )); then
sleep "$interval"
fi
done
#!/usr/bin/env bash
set -euo pipefail
body_file=""
pr=""
repo=""
usage() {
cat <<'USAGE'
Usage: pr-body-update.sh --file <path> [--pr <number>] [--repo <owner/repo>]
Updates a PR body using the GraphQL updatePullRequest mutation and verifies the result.
USAGE
}
while [[ $# -gt 0 ]]; do
case "$1" in
--file)
body_file="$2"
shift 2
;;
--pr)
pr="$2"
shift 2
;;
--repo)
repo="$2"
shift 2
;;
-h|--help)
usage
exit 0
;;
*)
echo "Unknown arg: $1" >&2
usage >&2
exit 1
;;
esac
done
if [[ -z "$body_file" ]]; then
echo "--file is required." >&2
exit 1
fi
if [[ ! -f "$body_file" ]]; then
echo "Body file not found: $body_file" >&2
exit 1
fi
if [[ ! -s "$body_file" ]]; then
echo "Body file is empty: $body_file" >&2
exit 1
fi
if [[ -z "$pr" ]]; then
pr="$(gh pr view --json number --jq .number 2>/dev/null || true)"
fi
if [[ -z "$pr" ]]; then
echo "Could not determine PR number. Use --pr <number>." >&2
exit 1
fi
if [[ -z "$repo" ]]; then
repo="$(gh repo view --json nameWithOwner --jq .nameWithOwner 2>/dev/null || true)"
fi
if [[ -z "$repo" ]]; then
echo "Could not determine repo. Use --repo owner/name." >&2
exit 1
fi
pr_id="$(gh pr view "$pr" --repo "$repo" --json id --jq .id 2>/dev/null || true)"
if [[ -z "$pr_id" ]]; then
echo "Could not determine PR id for #$pr in $repo." >&2
exit 1
fi
gh api graphql \
-f query='mutation($id:ID!,$body:String!){updatePullRequest(input:{pullRequestId:$id, body:$body}){pullRequest{id}}}' \
-f id="$pr_id" \
-f body="$(cat "$body_file")" \
>/dev/null
updated_body="$(gh pr view "$pr" --repo "$repo" --json body --jq .body 2>/dev/null || true)"
if [[ -z "$updated_body" ]]; then
echo "Failed to fetch updated PR body for #$pr in $repo." >&2
exit 1
fi
if [[ "$updated_body" != "$(cat "$body_file")" ]]; then
echo "PR body mismatch after update." >&2
exit 1
fi
echo "Updated PR #$pr body in $repo."
#!/usr/bin/env bash
set -euo pipefail
pr=""
repo=""
usage() {
cat <<'USAGE'
Usage: triage-pr.sh [--pr <number>] [--repo <owner/repo>]
Prints a single-shot summary of CI status, latest review, and latest comments.
USAGE
}
while [[ $# -gt 0 ]]; do
case "$1" in
--pr)
pr="$2"
shift 2
;;
--repo)
repo="$2"
shift 2
;;
-h|--help)
usage
exit 0
;;
*)
echo "Unknown arg: $1" >&2
usage >&2
exit 1
;;
esac
done
if [[ -z "$pr" ]]; then
pr="$(gh pr view --json number --jq .number 2>/dev/null || true)"
fi
if [[ -z "$pr" ]]; then
echo "Could not determine PR number. Use --pr <number>." >&2
exit 1
fi
if [[ -z "$repo" ]]; then
repo="$(gh repo view --json nameWithOwner --jq .nameWithOwner 2>/dev/null || true)"
fi
if [[ -z "$repo" ]]; then
echo "Could not determine repo. Use --repo owner/name." >&2
exit 1
fi
checks_counts=$(gh pr checks "$pr" --repo "$repo" --json status,conclusion --jq '
[length,
(map(select(.status != "COMPLETED")) | length),
(map(select(.conclusion != null and .conclusion != "SUCCESS")) | length),
(map(select(.conclusion == "SUCCESS")) | length)
] | @tsv
' 2>/dev/null || true)
if [[ -n "$checks_counts" ]]; then
IFS=$'\t' read -r total pending failed success <<< "$checks_counts"
echo "CI: total=$total pending=$pending failed=$failed success=$success"
else
echo "CI: unavailable"
fi
failed_checks=$(gh pr checks "$pr" --repo "$repo" --json name,conclusion,detailsUrl --jq '
[.[] | select(.conclusion != null and .conclusion != "SUCCESS") |
"\(.name)\t\(.conclusion)\t\(.detailsUrl // \"\")"
] | .[]
' 2>/dev/null || true)
if [[ -n "$failed_checks" ]]; then
while IFS=$'\t' read -r name conclusion url; do
if [[ -n "$name" ]]; then
echo "FAIL: $name $conclusion $url"
fi
done <<< "$failed_checks"
fi
review_line=$(gh api "repos/$repo/pulls/$pr/reviews?per_page=100" --jq '
[ .[] | select(.submitted_at != null) ] |
if length == 0 then "" else
(max_by(.submitted_at)) | "\(.state)\t\(.user.login)\t\(.submitted_at)\t\(.html_url)"
end
' 2>/dev/null || true)
if [[ -n "$review_line" ]]; then
IFS=$'\t' read -r r_state r_user r_time r_url <<< "$review_line"
echo "REVIEW: $r_state $r_user $r_time $r_url"
fi
issue_line=$(gh api "repos/$repo/issues/$pr/comments?per_page=100" --jq '
if length == 0 then "" else
(max_by(.created_at)) | "\(.user.login)\t\(.created_at)\t\(.html_url)\t\(.body | gsub("\\n"; " ") | gsub("\\t"; " ") | .[0:200])"
end
' 2>/dev/null || true)
if [[ -n "$issue_line" ]]; then
IFS=$'\t' read -r c_user c_time c_url c_body <<< "$issue_line"
echo "COMMENT: conversation $c_user $c_time $c_url $c_body"
fi
review_comment_line=$(gh api "repos/$repo/pulls/$pr/comments?per_page=100" --jq '
if length == 0 then "" else
(max_by(.created_at)) | "\(.user.login)\t\(.created_at)\t\(.html_url)\t\(.body | gsub("\\n"; " ") | gsub("\\t"; " ") | .[0:200])"
end
' 2>/dev/null || true)
if [[ -n "$review_comment_line" ]]; then
IFS=$'\t' read -r rc_user rc_time rc_url rc_body <<< "$review_comment_line"
echo "COMMENT: inline $rc_user $rc_time $rc_url $rc_body"
fi