Create Pull Request
Orchestrates the complete PR creation flow: commit → rebase → push → create → describe → link Linear ticket.
Prerequisites
# Check project setup (thoughts, CLAUDE.md snippet, config)
if [[ -f "${CLAUDE_PLUGIN_ROOT}/scripts/check-project-setup.sh" ]]; then
"${CLAUDE_PLUGIN_ROOT}/scripts/check-project-setup.sh" || exit 1
fi
Configuration
Read team configuration from .catalyst/config.json:
CONFIG_FILE=".catalyst/config.json"
[[ ! -f "$CONFIG_FILE" ]] && CONFIG_FILE=".claude/config.json"
TEAM_KEY=$(jq -r '.catalyst.linear.teamKey // "PROJ"' "$CONFIG_FILE")
Process:
1. Check for uncommitted changes
git status --porcelain
If there are uncommitted changes:
- Offer to commit: "You have uncommitted changes. Create commits now? [Y/n]"
- If yes: internally call
/commitworkflow - If no: proceed (user may want to commit manually later)
2. Verify not on main/master branch
branch=$(git branch --show-current)
If on main or master:
- Error: "Cannot create PR from main branch. Create a feature branch first."
- Exit
3. Detect base branch
# Check which exists
if git show-ref --verify --quiet refs/heads/main; then
base="main"
elif git show-ref --verify --quiet refs/heads/master; then
base="master"
else
base=$(git symbolic-ref refs/remotes/origin/HEAD | sed 's@^refs/remotes/origin/@@')
fi
4. Check if branch is up-to-date with base
# Fetch latest
git fetch origin $base
# Check if behind
if git log HEAD..origin/$base --oneline | grep -q .; then
echo "Branch is behind $base"
fi
If behind:
- Auto-rebase:
git rebase origin/$base - If conflicts:
- Show conflicting files
- Error: "Rebase conflicts detected. Resolve conflicts and run /catalyst-dev:create-pr again."
- Exit
5. Check for existing PR
gh pr view --json number,url,title,state 2>/dev/null
If PR exists:
- Show: "PR #{number} already exists: {title}\n{url}"
- Ask: "What would you like to do?\n [D] Describe/update this PR\n [S] Skip (do nothing)\n [A] Abort"
- If D: call
/describe-prand exit - If S: exit with success message
- If A: exit
- This is the ONLY interactive prompt in the happy path
6. Extract ticket from branch name
branch=$(git branch --show-current)
# Extract pattern: PREFIX-NUMBER using configured team key
if [[ "$branch" =~ ($TEAM_KEY-[0-9]+) ]]; then
ticket="${BASH_REMATCH[1]}" # e.g., ENG-123
fi
7. Generate PR title from branch and ticket
PR titles follow <type>(<scope>): <ticket> ... so active work is identifiable from GitHub
alone. Prefer the first commit subject (it carries type/scope per commit conventions); inject
the ticket via draft_pr_title. Branch-derived title remains the no-commit fallback.
# CTL-783: PR titles follow `<type>(<scope>): <ticket> ...` convention.
# Prefer first commit subject; branch-derived title is the no-commit fallback.
source "${CLAUDE_PLUGIN_ROOT}/scripts/lib/draft-pr.sh"
commit_subj=$(git log --no-merges --format='%s' "origin/${base}..HEAD" 2>/dev/null | tail -1)
if [[ -n "$commit_subj" ]]; then
title="$(draft_pr_title "$ticket" "$commit_subj")"
else
# Branch-derived fallback (no commits or base unreachable)
if [[ "$ticket" ]]; then
desc=$(echo "$branch" | sed "s/^$ticket-//")
desc=$(echo "$desc" | tr '-' ' ')
title="$ticket: $desc"
else
desc=$(echo "$branch" | tr '-' ' ')
title="$desc"
fi
fi
8. Push branch
# Push current HEAD and verify origin == HEAD. On a non-fast-forward (branch
# rebased/amended after a prior push), retry with --force-with-lease so the PR
# never points at a stale commit (CTL-1051).
BRANCH="$(git rev-parse --abbrev-ref HEAD)"
if ! git push -u origin HEAD; then
echo "create-pr: fast-forward push failed; retrying with --force-with-lease" >&2
git push --force-with-lease -u origin HEAD
fi
git fetch --quiet origin "$BRANCH" || true
if [[ "$(git rev-parse "origin/${BRANCH}")" != "$(git rev-parse HEAD)" ]]; then
echo "create-pr: post-push verify failed — origin/${BRANCH} != HEAD" >&2
exit 1
fi
9. Create PR
CRITICAL: NO CLAUDE ATTRIBUTION
DO NOT add any of the following to the PR:
- ❌ "Generated with Claude Code" or similar messages
- ❌ "Co-Authored-By: Claude" lines
- ❌ Any reference to AI assistance
- ❌ Links to Claude Code or Anthropic
The PR should be authored solely by the user (git author). Keep the description clean and professional.
# Generate a meaningful initial body from commit messages (NO CLAUDE ATTRIBUTION)
commits=$(git log origin/$base..HEAD --oneline --no-merges)
body="## Changes
$commits"
# If ticket exists, add reference
if [[ "$ticket" ]]; then
body="$body
Refs: $ticket"
fi
# CTL-623: prevent Linear from auto-linking sibling tickets embedded in the
# branch name (multi-ticket orchestrator runs build branches like
# `o-adv-1155-1156-1157-ADV-1155`) and dragging their workflow status when this
# PR opens. The skip/ignore negative magic word fully unlinks siblings even when
# the branch still carries their IDs (https://linear.app/docs/github). No-op for
# single-ticket branches. Linking can fire on PR-open, BEFORE /describe-pr runs,
# so the guard block must be present in this transient initial body too.
# CTL-633: create-pr scans the BRANCH only — the transient body is assembled
# from commit messages, not user prose, so no body-mode scan is needed (and
# adding one risks fabricating from commit subjects). Call _from_branch
# explicitly to opt into the new mode-aware API.
# shellcheck source=/dev/null
source "${CLAUDE_PLUGIN_ROOT}/scripts/lib/linear-pr-skip.sh"
skip_block="$(linear_sibling_skip_block_from_branch "$ticket" "$branch")"
[[ -n "$skip_block" ]] && body="$body
$skip_block"
# Create PR (author will be the git user)
gh pr create --title "$title" --body "$body" --base "$base"
The initial body uses commit messages so the PR is immediately readable even before /describe-pr
generates the full description.
Capture PR number and URL from output.
Track in Workflow Context (REQUIRED)
After creating the PR, track it — substitute the actual PR URL and ticket:
"${CLAUDE_PLUGIN_ROOT}/scripts/workflow-context.sh" add prs "https://github.com/org/repo/pull/NUMBER" "TICKET-ID"
10. Auto-call /describe-pr
Immediately call /describe-pr with the PR number to:
- Generate comprehensive description
- Run verification checks
- Update PR title (refined from code analysis)
- Save to thoughts/
- Update Linear ticket
11. Update Linear ticket (if ticket found)
If ticket was extracted from branch:
# If Linearis CLI is available:
# 1. Update ticket status to stateMap.inReview from config
# 2. Add a comment with the PR link
# Use `linearis issues usage` and `linearis comments usage` for exact syntax.
# Skip silently if CLI not available.
Skip the status transition (step 1) when CATALYST_PHASE is set — under a
phase agent the deterministic coordinator (CTL-558) owns the Linear status
write-back (the execution-core scheduler / orchestrate-phase-advance writes
the inReview-equivalent state on the pr phase). This status transition is
only for interactive /catalyst-dev:create-pr use; the PR-link comment (step 2)
is still posted in both modes.
12. Post-PR Monitoring & Resolution Loop
CRITICAL: Creating the PR is NOT the end of this skill. You MUST monitor CI checks, wait for automated reviewer comments, address them, and only report success when the PR is in a clean, mergeable state — or genuinely blocked on a human gate (like approval from a specific person).
Do NOT just say "PR created" or "PR created with auto-merge" and stop. That leaves the user to do all the follow-up work manually.
Step 12a: Wait for CI checks and automated reviewers (event-driven)
Automated review agents (Codex, security scanners, linters