GitHub PR Workflow: Branch, Commit, Open, CI, Merge
GitHub PR lifecycle: branch, commit, open, CI, merge.
Written by Neura Market from the official Hermes Agent documentation for Github Pr Workflow. Commands, paths, and version numbers are reproduced from the source unchanged.
Read the official documentationThis guide walks you through the entire pull request lifecycle on GitHub: creating a branch, committing changes, opening a PR, monitoring CI, fixing failures, and merging. It's built for developers who want a repeatable, scriptable process, and it covers two ways to work: the gh CLI when it's available, and a git plus curl fallback for machines without it. If you're automating PRs with an agent or just want a reliable manual routine, this is for you.
What it does
This skill turns the PR lifecycle into a sequence of concrete steps. You start from a clean main, create a feature branch, make commits with conventional messages, push, open a PR, watch CI until it's green, fix any failures, and merge with a squash. Every step has a gh version and a git + curl version, so you can use the same workflow whether or not the GitHub CLI is installed.
The workflow is designed to be run by an autonomous agent, but it works just as well for a human at a terminal. The commands are copy-paste ready, and the fallback paths use the GitHub REST API directly, so you're never stuck if gh isn't present.
Before you start
You need two things in place before this workflow makes sense:
- You're authenticated with GitHub. The
github-authskill covers this, and the workflow assumes you've already done it. - You're inside a git repository that has a GitHub remote.
The skill includes a quick auth detection snippet that decides which method to use. It checks for gh and, if that's missing, looks for a GITHUB_TOKEN in your environment or in the Hermes .env file. If neither is found, it tries to extract a token from ~/.git-credentials using a helper script. This means you don't have to think about which path you're on; the workflow picks it for you.
Quick Auth Detection
# Determine which method to use throughout this workflow
if command -v gh &>/dev/null && gh auth status &>/dev/null; then
AUTH="gh"
else
AUTH="git"
# Ensure we have a token for API calls
if [ -z "$GITHUB_TOKEN" ]; then
if _hermes_env="${HERMES_HOME:-$HOME/.hermes}/.env"; [ -f "$_hermes_env" ] && grep -q "^GITHUB_TOKEN=" "$_hermes_env"; then
GITHUB_TOKEN=$(grep "^GITHUB_TOKEN=" "$_hermes_env" | head -1 | cut -d= -f2 | tr -d '\n\r')
elif grep -q "github.com" ~/.git-credentials 2>/dev/null; then
GITHUB_TOKEN=$(uv run python "${HERMES_HOME:-$HOME/.hermes}/skills/github/github-auth/scripts/git-credential-token.py")
fi
fi
fi
echo "Using: $AUTH"
Run this once at the start of your session. It sets the AUTH variable to either gh or git, and it ensures GITHUB_TOKEN is populated if you're on the fallback path. The echo at the end tells you which mode you're in.
Extracting Owner/Repo from the Git Remote
Many of the curl commands need the owner/repo pair. Instead of hardcoding it, you can pull it from your remote URL:
# Works for both HTTPS and SSH remote URLs
REMOTE_URL=$(git remote get-url origin)
OWNER_REPO=$(echo "$REMOTE_URL" | sed -E 's|.*github\.com[:/]||; s|\.git$||')
OWNER=$(echo "$OWNER_REPO" | cut -d/ -f1)
REPO=$(echo "$OWNER_REPO" | cut -d/ -f2)
echo "Owner: $OWNER, Repo: $REPO"
This handles both https://github.com/owner/repo.git and git@github.com:owner/repo.git formats. After this, $OWNER and $REPO are available for the REST calls.
1. Branch Creation
Branch creation is pure git, so there's no difference between the gh and curl paths. The goal is to start from an up-to-date main and create a descriptive branch name.
# Make sure you're up to date
git fetch origin
git checkout main && git pull origin main
# Create and switch to a new branch
git checkout -b feat/add-user-authentication
The branch name should follow a convention that makes the purpose clear at a glance. The skill suggests these prefixes:
feat/description, new featuresfix/description, bug fixesrefactor/description, code restructuringdocs/description, documentationci/description, CI/CD changes
Using a consistent prefix helps with filtering and automation later.
2. Making Commits
Once you're on your branch, make your code changes. The skill assumes you're using the agent's file tools (write_file, patch) to edit files, but you can also edit manually. After changes are in place, stage the specific files you touched and commit with a conventional message.
# Stage specific files
git add src/auth.py src/models/user.py tests/test_auth.py
# Commit with a conventional commit message
git commit -m "feat: add JWT-based user authentication
- Add login/register endpoints
- Add User model with password hashing
- Add auth middleware for protected routes
- Add unit tests for auth flow"
The commit message format follows Conventional Commits, which makes the history readable and enables automated tooling:
type(scope): short description
Longer explanation if needed. Wrap at 72 characters.
Types: feat, fix, refactor, docs, test, ci, chore, perf
The type goes at the start, followed by an optional scope in parentheses, then a colon and a short description. The body, if any, comes after a blank line and should wrap at 72 characters.
3. Pushing and Creating a PR
Push the Branch (same either way)
git push -u origin HEAD
The -u sets the upstream, so subsequent pushes on this branch can just be git push.
Create the PR
With gh:
gh pr create \
--title "feat: add JWT-based user authentication" \
--body "## Summary
- Adds login and register API endpoints
- JWT token generation and validation
## Test Plan
- [ ] Unit tests pass
Closes #42"
Options: --draft, --reviewer user1,user2, --label "enhancement", --base develop
These options let you create a draft PR, request specific reviewers, add labels, or target a different base branch than the default.
With git + curl:
BRANCH=$(git branch --show-current)
curl -s -X POST \
-H "Authorization: token $GITHUB_TOKEN" \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/$OWNER/$REPO/pulls \
-d "{
\"title\": \"feat: add JWT-based user authentication\",
\"body\": \"## Summary\nAdds login and register API endpoints.\n\nCloses #42\",
\"head\": \"$BRANCH\",
\"base\": \"main\"
}"
The response JSON includes the PR number, save it for later commands.
To create as a draft, add "draft": true to the JSON body.
Note that the curl version requires $OWNER, $REPO, and $GITHUB_TOKEN to be set, which you did in the setup steps.
4. Monitoring CI Status
After the PR is open, you need to know whether CI passes. This section shows how to check status and, if needed, wait for it to finish.
Check CI Status
With gh:
# One-shot check
gh pr checks
# Watch until all checks finish (polls every 10s)
gh pr checks --watch
The one-shot version prints the current state of all checks. The --watch version keeps polling every 10 seconds until every check completes, which is handy when you're waiting for a long build.
With git + curl:
# Get the latest commit SHA on the current branch
SHA=$(git rev-parse HEAD)
# Query the combined status
curl -s \
-H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/$OWNER/$REPO/commits/$SHA/status \
| python -c "
import sys, json
data = json.load(sys.stdin)
print(f\"Overall: {data['state']}\")
for s in data.get('statuses', []):
print(f\" {s['context']}: {s['state']} - {s.get('description', '')}\")"
# Also check GitHub Actions check runs (separate endpoint)
curl -s \
-H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/$OWNER/$REPO/commits/$SHA/check-runs \
| python -c "
import sys, json
data = json.load(sys.stdin)
for cr in data.get('check_runs', []):
print(f\" {cr['name']}: {cr['status']} / {cr['conclusion'] or 'pending'}\")"
The first call hits the combined status endpoint, which aggregates commit statuses. The second hits the check-runs endpoint, which covers GitHub Actions and other checks. You need both to see the full picture.
Poll Until Complete (git + curl)
# Simple polling loop — check every 30 seconds, up to 10 minutes
SHA=$(git rev-parse HEAD)
for i in $(seq 1 20); do
STATUS=$(curl -s \
-H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/$OWNER/$REPO/commits/$SHA/status \
| python -c "import sys,json; print(json.load(sys.stdin)['state'])")
echo "Check $i: $STATUS"
if [ "$STATUS" = "success" ] || [ "$STATUS" = "failure" ] || [ "$STATUS" = "error" ]; then
break
fi
sleep 30
done
This loop checks every 30 seconds, up to 20 times (10 minutes total). It stops as soon as the status is success, failure, or error, so you're not waiting longer than necessary.
5. Auto-Fixing CI Failures
When CI fails, you need to diagnose and fix it. This section gives you a loop that works with either auth method.
Step 1: Get Failure Details
With gh:
# List recent workflow runs on this branch
gh run list --branch $(git branch --show-current) --limit 5
# View failed logs
gh run view <RUN_ID> --log-failed
First, list the recent runs to find the failed one. Then view the logs for that run, filtering to only the failed steps.
With git + curl:
BRANCH=$(git branch --show-current)
# List workflow runs on this branch
curl -s \
-H "Authorization: token $GITHUB_TOKEN" \
"https://api.github.com/repos/$OWNER/$REPO/actions/runs?branch=$BRANCH&per_page=5" \
| python -c "
import sys, json
runs = json.load(sys.stdin)['workflow_runs']
for r in runs:
print(f\"Run {r['id']}: {r['name']} - {r['conclusion'] or r['status']}\")"
# Get failed job logs (download as zip, extract, read)
RUN_ID=<run_id>
curl -s -L \
-H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/$OWNER/$REPO/actions/runs/$RUN_ID/logs \
-o /tmp/ci-logs.zip
cd /tmp && unzip -o ci-logs.zip -d ci-logs && cat ci-logs/*.txt
The first command lists the workflow runs on your branch, showing the run ID and conclusion. The second downloads the logs as a zip, extracts them, and prints the text files so you can see what went wrong.
Step 2: Fix and Push
After identifying the issue, use file tools (patch, write_file) to fix it:
git add <fixed_files>
git commit -m "fix: resolve CI failure in <check_name>"
git push
Stage the files you changed, commit with a message that references the check you're fixing, and push. The push triggers CI again on the updated branch.
Step 3: Verify
Re-check CI status using the commands from Section 4 above.
Auto-Fix Loop Pattern
When asked to auto-fix CI, follow this loop:
- Check CI status → identify failures
- Read failure logs → understand the error
- Use
read_file+patch/write_file→ fix the code git add . && git commit -m "fix: ..." && git push- Wait for CI → re-check status
- Repeat if still failing (up to 3 attempts, then ask the user)
This loop is designed to be run by an agent, but it's also a good manual discipline. The key limit is three attempts; after that, you should ask a human for help rather than churning.
6. Merging
Once CI is green, you can merge. The skill recommends squash merging for feature branches, which keeps history clean.
With gh:
# Squash merge + delete branch (cleanest for feature branches)
gh pr merge --squash --delete-branch
# Enable auto-merge (merges when all checks pass)
gh pr merge --auto --squash --delete-branch
The first command merges and deletes the remote branch. The second enables auto-merge, so the PR merges automatically once all required checks pass.
With git + curl:
PR_NUMBER=<number>
# Merge the PR via API (squash)
curl -s -X PUT \
-H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/$OWNER/$REPO/pulls/$PR_NUMBER/merge \
-d "{
\"merge_method\": \"squash\",
\"commit_title\": \"feat: add user authentication (#$PR_NUMBER)\"
}"
# Delete the remote branch after merge
BRANCH=$(git branch --show-current)
git push origin --delete $BRANCH
# Switch back to main locally
git checkout main && git pull origin main
git branch -d $BRANCH
Merge methods: "merge" (merge commit), "squash", "rebase"
The API call merges the PR with the specified method. After that, you delete the remote branch, switch back to main, pull the latest, and delete the local branch.
Enable Auto-Merge (curl)
# Auto-merge requires the repo to have it enabled in settings.
# This uses the GraphQL API since REST doesn't support auto-merge.
PR_NODE_ID=$(curl -s \
-H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/$OWNER/$REPO/pulls/$PR_NUMBER \
| python -c "import sys,json; print(json.load(sys.stdin)['node_id'])")
curl -s -X POST \
-H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/graphql \
-d "{\"query\": \"mutation { enablePullRequestAutoMerge(input: {pullRequestId: \\\"$PR_NODE_ID\\\", mergeMethod: SQUASH}) { clientMutationId } }\"}"
Auto-merge requires the repository to have it enabled in settings. The REST API doesn't support auto-merge, so this uses the GraphQL API. It first fetches the PR's node ID, then sends a mutation to enable auto-merge with the squash method.
7. Complete Workflow Example
Here's the whole flow in one place, from clean main to merged PR:
# 1. Start from clean main
git checkout main && git pull origin main
# 2. Branch
git checkout -b fix/login-redirect-bug
# 3. (Agent makes code changes with file tools)
# 4. Commit
git add src/auth/login.py tests/test_login.py
git commit -m "fix: correct redirect URL after login
Preserves the ?next= parameter instead of always redirecting to /dashboard."
# 5. Push
git push -u origin HEAD
# 6. Create PR (picks gh or curl based on what's available)
# ... (see Section 3)
# 7. Monitor CI (see Section 4)
# 8. Merge when green (see Section 6)
This example uses a fix/ branch and a conventional commit message. The comments point you to the relevant sections for the steps that aren't shown inline.
Useful PR Commands Reference
The table below summarizes common PR actions and their gh and git + curl equivalents. The curl commands assume $GITHUB_TOKEN, $OWNER, and $REPO are set.
| Action | gh | git + curl |
|---|---|---|
| List my PRs | gh pr list --author @me | curl -s -H "Authorization: token $GITHUB_TOKEN" "https://api.github.com/repos/$OWNER/$REPO/pulls?state=open" |
| View PR diff | gh pr diff | git diff main...HEAD (local) or curl -H "Accept: application/vnd.github.diff" ... |
| Add comment | gh pr comment N --body "..." | curl -X POST .../issues/N/comments -d '{"body":"..."}' |
| Request review | gh pr edit N --add-reviewer user | curl -X POST .../pulls/N/requested_reviewers -d '{"reviewers":["user"]}' |
| Close PR | gh pr close N | curl -X PATCH .../pulls/N -d '{"state":"closed"}' |
| Check out someone's PR | gh pr checkout N | git fetch origin pull/N/head:pr-N && git checkout pr-N |
When not to use it
The source doesn't list explicit exclusions, but the workflow assumes a GitHub remote and a standard branch-based flow. If you're working with a different hosting platform (GitLab, Bitbucket) or a trunk-based development model without PRs, this won't apply directly. Also, if you're in an environment where you can't install gh and don't have a GITHUB_TOKEN, the fallback won't work either.
Limits and gotchas
- The auto-merge feature via
curlrequires the repository to have auto-merge enabled in its settings. If it's not enabled, the GraphQL mutation will fail. - The polling loop in Section 4 runs for a maximum of 10 minutes (20 checks at 30-second intervals). If CI takes longer, the loop exits without a final status, and you'll need to check manually.
- The auto-fix loop is capped at three attempts. After that, the skill asks the user rather than continuing to push fixes.
- The
curlfallback depends on$GITHUB_TOKENbeing set. The auth detection snippet tries to source it from the Hermes.envfile or~/.git-credentials, but if neither exists, you'll need to export it manually. - The
gh pr checks --watchcommand polls every 10 seconds, which could be noisy in a long-running session.
Related skills
This skill is part of a broader GitHub toolkit. It pairs with github-auth, which handles the authentication that this workflow assumes, and github-code-review, which covers reviewing PRs rather than just creating and merging them. If you're automating a full PR cycle, you'll likely want all three.