BundledGitHubVersion 1.1.0

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 documentation

This 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-auth skill 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 features
  • fix/description, bug fixes
  • refactor/description, code restructuring
  • docs/description, documentation
  • ci/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:

  1. Check CI status → identify failures
  2. Read failure logs → understand the error
  3. Use read_file + patch/write_file → fix the code
  4. git add . && git commit -m "fix: ..." && git push
  5. Wait for CI → re-check status
  6. 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.

Actionghgit + curl
List my PRsgh pr list --author @mecurl -s -H "Authorization: token $GITHUB_TOKEN" "https://api.github.com/repos/$OWNER/$REPO/pulls?state=open"
View PR diffgh pr diffgit diff main...HEAD (local) or curl -H "Accept: application/vnd.github.diff" ...
Add commentgh pr comment N --body "..."curl -X POST .../issues/N/comments -d '{"body":"..."}'
Request reviewgh pr edit N --add-reviewer usercurl -X POST .../pulls/N/requested_reviewers -d '{"reviewers":["user"]}'
Close PRgh pr close Ncurl -X PATCH .../pulls/N -d '{"state":"closed"}'
Check out someone's PRgh pr checkout Ngit 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 curl requires 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 curl fallback depends on $GITHUB_TOKEN being set. The auth detection snippet tries to source it from the Hermes .env file or ~/.git-credentials, but if neither exists, you'll need to export it manually.
  • The gh pr checks --watch command 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.

Skills the docs pair this with

More GitHub skills