329 lines
8.2 KiB
Markdown
329 lines
8.2 KiB
Markdown
---
|
|
description: Create PR as draft, wait for CI to pass, then mark ready for review (solves the "I'll check back later" problem)
|
|
---
|
|
|
|
# Creating PR with CI Verification
|
|
|
|
**IMPORTANT:** This command solves the common problem where Claude creates a PR, says "I'll wait for CI", then gives up. This command ACTUALLY waits for CI completion.
|
|
|
|
## Workflow
|
|
|
|
This command will:
|
|
1. ✅ Create PR as draft
|
|
2. ✅ Wait for CI checks to complete (actually wait, not give up)
|
|
3. ✅ Mark as ready for review if CI passes
|
|
4. ✅ Report failures with links if CI fails
|
|
|
|
## Step 1: Determine Current Branch and Changes
|
|
|
|
```bash
|
|
# Get current branch
|
|
CURRENT_BRANCH=$(git branch --show-current)
|
|
|
|
# Get base branch (usually main or master)
|
|
BASE_BRANCH=$(git remote show origin | grep 'HEAD branch' | cut -d' ' -f5)
|
|
|
|
# Verify we're not on main/master
|
|
if [[ "$CURRENT_BRANCH" == "main" || "$CURRENT_BRANCH" == "master" ]]; then
|
|
echo "ERROR: Cannot create PR from main/master branch"
|
|
echo "Create a feature branch first: git checkout -b feature-name"
|
|
exit 1
|
|
fi
|
|
|
|
# Check if branch is pushed
|
|
git rev-parse --verify origin/$CURRENT_BRANCH > /dev/null 2>&1
|
|
if [ $? -ne 0 ]; then
|
|
echo "Branch not pushed yet. Pushing now..."
|
|
git push -u origin $CURRENT_BRANCH
|
|
fi
|
|
```
|
|
|
|
## Step 2: Gather PR Context
|
|
|
|
Run in parallel to gather information:
|
|
|
|
```bash
|
|
# 1. Git status
|
|
git status
|
|
|
|
# 2. Diff since divergence from base
|
|
git diff ${BASE_BRANCH}...HEAD
|
|
|
|
# 3. Commit history for this branch
|
|
git log ${BASE_BRANCH}..HEAD --oneline
|
|
|
|
# 4. Check if PR already exists
|
|
gh pr view --json number,state,isDraft 2>/dev/null
|
|
```
|
|
|
|
**If PR already exists:**
|
|
```bash
|
|
EXISTING_PR=$(gh pr view --json number --jq '.number')
|
|
echo "PR #$EXISTING_PR already exists for this branch"
|
|
echo "Use /review-pr $EXISTING_PR or /fix-pr $EXISTING_PR instead"
|
|
exit 0
|
|
```
|
|
|
|
## Step 3: Create PR Title and Body
|
|
|
|
Based on the changes (read from git diff and commit history):
|
|
|
|
**Title:** Concise summary of changes (50 chars max)
|
|
|
|
**Body format:**
|
|
```markdown
|
|
## Summary
|
|
- Bullet point 1
|
|
- Bullet point 2
|
|
|
|
## Changes
|
|
- Specific change 1
|
|
- Specific change 2
|
|
|
|
## Testing
|
|
- Test approach
|
|
- Verification steps
|
|
|
|
## Checklist
|
|
- [ ] Tests pass locally
|
|
- [ ] Code formatted (make format)
|
|
- [ ] Changelog entry created (if applicable)
|
|
|
|
---
|
|
|
|
🤖 Generated with [Claude Code](https://claude.com/claude-code)
|
|
|
|
Co-Authored-By: Claude <noreply@anthropic.com>
|
|
```
|
|
|
|
## Step 4: Create PR as Draft
|
|
|
|
**CRITICAL:** Always create as draft initially.
|
|
|
|
```bash
|
|
# Create PR as draft
|
|
gh pr create \
|
|
--title "PR Title Here" \
|
|
--body "$(cat <<'EOF'
|
|
PR body here
|
|
EOF
|
|
)" \
|
|
--draft
|
|
|
|
# Get PR number
|
|
PR_NUMBER=$(gh pr view --json number --jq '.number')
|
|
echo "Created draft PR #$PR_NUMBER"
|
|
echo "URL: $(gh pr view --json url --jq '.url')"
|
|
```
|
|
|
|
## Step 5: Wait for CI Checks (DO NOT GIVE UP!)
|
|
|
|
**This is where Claude usually fails. DO NOT say "I'll check back later".**
|
|
|
|
**Instead, ACTUALLY wait using a polling loop with NO timeout:**
|
|
|
|
```bash
|
|
echo "Waiting for CI checks to complete..."
|
|
echo "Typical CI times by repository:"
|
|
echo " - policyengine-app: 3-6 minutes"
|
|
echo " - policyengine-uk: 9-12 minutes"
|
|
echo " - policyengine-api: 30 minutes"
|
|
echo " - policyengine-us: 60-75 minutes (longest)"
|
|
echo ""
|
|
echo "I will poll every 15 seconds until all checks complete. No time limit."
|
|
|
|
POLL_INTERVAL=15 # Check every 15 seconds
|
|
ELAPSED=0
|
|
|
|
while true; do
|
|
# Get check status
|
|
CHECKS_JSON=$(gh pr checks $PR_NUMBER --json name,status,conclusion)
|
|
|
|
# Count checks
|
|
TOTAL=$(echo "$CHECKS_JSON" | jq '. | length')
|
|
|
|
if [ "$TOTAL" -eq 0 ]; then
|
|
echo "No CI checks found yet. Waiting for checks to start..."
|
|
sleep $POLL_INTERVAL
|
|
ELAPSED=$((ELAPSED + POLL_INTERVAL))
|
|
continue
|
|
fi
|
|
|
|
# Count by status
|
|
COMPLETED=$(echo "$CHECKS_JSON" | jq '[.[] | select(.status == "COMPLETED")] | length')
|
|
FAILED=$(echo "$CHECKS_JSON" | jq '[.[] | select(.conclusion == "FAILURE")] | length')
|
|
|
|
echo "[$ELAPSED s] CI Checks: $COMPLETED/$TOTAL completed, $FAILED failed"
|
|
|
|
# If all completed
|
|
if [ "$COMPLETED" -eq "$TOTAL" ]; then
|
|
if [ "$FAILED" -eq 0 ]; then
|
|
echo "✅ All CI checks passed after $ELAPSED seconds!"
|
|
break
|
|
else
|
|
echo "❌ Some CI checks failed after $ELAPSED seconds."
|
|
# Show failures
|
|
gh pr checks $PR_NUMBER
|
|
break
|
|
fi
|
|
fi
|
|
|
|
# Continue waiting (no timeout!)
|
|
sleep $POLL_INTERVAL
|
|
ELAPSED=$((ELAPSED + POLL_INTERVAL))
|
|
done
|
|
```
|
|
|
|
**Important:** No timeout means we actually wait as long as needed. Population simulations can take 30+ minutes.
|
|
|
|
## Step 6: Mark Ready for Review (If CI Passed)
|
|
|
|
```bash
|
|
if [ "$FAILED" -eq 0 ] && [ "$COMPLETED" -eq "$TOTAL" ]; then
|
|
echo "Marking PR as ready for review..."
|
|
gh pr ready $PR_NUMBER
|
|
|
|
echo ""
|
|
echo "✅ PR #$PR_NUMBER is ready for review!"
|
|
echo "URL: $(gh pr view $PR_NUMBER --json url --jq '.url')"
|
|
echo ""
|
|
echo "All CI checks passed:"
|
|
gh pr checks $PR_NUMBER
|
|
else
|
|
echo ""
|
|
echo "⚠️ PR remains as draft due to CI issues."
|
|
echo "URL: $(gh pr view $PR_NUMBER --json url --jq '.url')"
|
|
echo ""
|
|
echo "CI status:"
|
|
gh pr checks $PR_NUMBER
|
|
echo ""
|
|
echo "To fix and retry:"
|
|
echo "1. Fix the failing checks"
|
|
echo "2. Push changes: git push"
|
|
echo "3. Run this command again to re-check CI"
|
|
fi
|
|
```
|
|
|
|
|
|
## Key Differences from Default Behavior
|
|
|
|
**Old way (Claude gives up):**
|
|
```
|
|
Claude: "I've created the PR. CI will take a while, I'll check back later..."
|
|
[Chat ends, Claude never checks back]
|
|
User: Has to manually check CI and mark ready
|
|
```
|
|
|
|
**New way (this command actually waits):**
|
|
```
|
|
Claude: "I've created the PR as draft. Now waiting for CI..."
|
|
[Actually polls CI status every 15 seconds]
|
|
Claude: "CI passed! Marking as ready for review."
|
|
[PR is ready, user doesn't have to do anything]
|
|
```
|
|
|
|
## Error Handling
|
|
|
|
**If CI fails:**
|
|
```bash
|
|
echo "❌ CI checks failed. Here are the failures:"
|
|
gh pr checks $PR_NUMBER
|
|
|
|
echo ""
|
|
echo "Common fixes:"
|
|
echo "- Linting errors: make format && git push"
|
|
echo "- Test failures: See logs at PR URL"
|
|
echo "- Use /fix-pr $PR_NUMBER to attempt automated fixes"
|
|
```
|
|
|
|
**If no CI configured:**
|
|
```bash
|
|
if [ "$TOTAL" -eq 0 ] && [ $ELAPSED -gt 60 ]; then
|
|
echo "⚠️ No CI checks detected after 60 seconds."
|
|
echo "Repository may not have CI configured."
|
|
echo "Marking PR as ready for manual review."
|
|
gh pr ready $PR_NUMBER
|
|
fi
|
|
```
|
|
|
|
## Usage Examples
|
|
|
|
**Basic:**
|
|
```bash
|
|
# Make changes, commit
|
|
git add .
|
|
git commit -m "Add feature"
|
|
|
|
# Create PR (command waits for CI)
|
|
/create-pr
|
|
```
|
|
|
|
**With custom title:**
|
|
```bash
|
|
# Command can accept optional arguments
|
|
/create-pr "Add California EITC implementation"
|
|
```
|
|
|
|
**Checking existing PR:**
|
|
```bash
|
|
# If PR exists, command tells you and suggests alternatives
|
|
/create-pr
|
|
# Output: "PR #123 exists. Use /review-pr 123 or /fix-pr 123"
|
|
```
|
|
|
|
## Integration with Other Commands
|
|
|
|
**After /encode-policy or /fix-pr:**
|
|
```bash
|
|
# These commands might push code
|
|
# Use /create-pr to create PR and wait for CI
|
|
/create-pr
|
|
```
|
|
|
|
**After fixing CI issues:**
|
|
```bash
|
|
# Fix issues locally
|
|
make format
|
|
git add .
|
|
git commit -m "Fix linting"
|
|
git push
|
|
|
|
# Command will detect existing PR and re-check CI
|
|
/create-pr # "PR #123 exists, checking CI status..."
|
|
```
|
|
|
|
## CI Duration Expectations
|
|
|
|
**By repository (based on actual data):**
|
|
- **policyengine-app:** 3-6 minutes (fast)
|
|
- **policyengine-uk:** 9-12 minutes (medium)
|
|
- **policyengine-api:** ~30 minutes (slow)
|
|
- **policyengine-us:** 60-75 minutes (very slow - population simulations)
|
|
|
|
**The command has no timeout** - it will wait as long as needed, even for policyengine-us's 75-minute CI runs.
|
|
|
|
## Why This Works
|
|
|
|
**Problem:** Claude's internal timeout or context leads it to give up
|
|
|
|
**Solution:**
|
|
1. ✅ Explicit polling loop (Claude sees the code will wait)
|
|
2. ✅ Clear status updates (Claude knows it's still working)
|
|
3. ✅ Timeout is explicit (Claude knows how long to wait)
|
|
4. ✅ Fallback handling (What to do if timeout occurs)
|
|
|
|
**Key insight:** By having the command contain the waiting logic in bash, Claude executes it and actually waits instead of just saying "I'll wait."
|
|
|
|
## Notes
|
|
|
|
**This command is essential for:**
|
|
- Automated PR workflows
|
|
- CI-dependent merges
|
|
- Team workflows requiring CI validation
|
|
- Reducing manual PR management
|
|
|
|
**Use this instead of:**
|
|
- Manual `gh pr create` followed by manual CI checking
|
|
- Asking Claude to "wait for CI" (which it can't do reliably)
|
|
- Creating ready PRs before CI passes
|