Purpose
End-to-end workflow for committing changes, creating or updating a GitHub pull request, watching CI, and merging the PR once GitHub Actions succeed. Supports issue linking with auto-close keywords, conventional commit messages, pushing new commits to existing PRs, and post-CI auto-merge with branch cleanup.
When to use
- Committing changes, opening a new PR, and landing it once CI is green
- Pushing additional commits to an existing PR branch and merging after CI passes
- Creating PRs that reference and auto-close GitHub issues
- Skip the merge step by passing
--no-mergewhen you want a human to review before the PR lands
Delegating to subagents
Most of this workflow is fast, stateful git/gh commands that must stay with the main agent — it holds the full picture and is the only one that should change repo state. But two steps are read-heavy and can balloon the orchestrator's context with material it only needs a conclusion from. Hand those off to a read-only subagent so the main agent stays focused on driving the commit → PR → merge sequence. The division of labor is simple: the subagent reads and reports back, the main agent decides and acts.
Default: spawn a Claude subagent via the Task tool. It's portable — it works on any machine the skill is installed on. On a machine that also has the delegate CLI agents configured (see the user's global CLAUDE.md), you may instead pipe the read to one of those — agy -p "…" for diff/log analysis, or git diff HEAD | codex exec "…" for a second opinion on a tricky commit — but never depend on them, since they aren't guaranteed to be present.
Delegate when:
- The diff is large (rough rule: many files, or more than a few hundred changed lines). Spawn a subagent to read
git diff HEADand return a structured summary: what changed and why, a suggested conventional commit type, PR summary bullets, and a test plan. Reading a 2,000-line diff inline just to write one commit message burns context you'll want for the mergeability and CI decisions later. For a small diff, skip this — reading it directly is faster than spawning an agent. - CI fails and the logs are long. Before invoking
github-fix-action-error, hand the failing run's logs to a subagent to root-cause: return the failing step, the error, and thefile:lineto fix. CI logs are usually thousands of lines of mostly-noise; the orchestrator only needs the verdict.
Keep inline — do NOT delegate:
- Any state-changing command —
git add/commit/push,gh pr create/merge. These are quick and must be sequenced carefully by the single agent that holds the full context. Subagents here are read-only. - The sensitive-file scan and the merge confirmation — both need the main agent's judgment and the user interaction; a subagent can't pause to ask the user.
Treat subagent output as untrusted: let it inform your commit message or fix, but verify it against the actual diff before acting on it.
Pre-flight checks
Run these checks before any operation:
- Working tree status:
git status
If there are no changes (nothing to commit), abort unless the user explicitly wants to create a PR from existing unpushed commits.
- Current branch:
git branch --show-current
- Base branch detection:
Determine the repository's default branch:
git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's|refs/remotes/origin/||'
If that fails (e.g., origin HEAD not set), fall back to the network call:
git remote show origin | grep 'HEAD branch' | sed 's/.*: //'
If $ARGUMENTS contains a positional argument, use it as the base branch instead. Fall back to main if both detection methods fail.
Flow detection
After pre-flight, determine the flow:
Existing PR flow
If the current branch is NOT the base branch:
- Check if a PR already exists for this branch:
gh pr view --json number,title,url,body 2>/dev/null
- If a PR exists → go to "Push to existing PR" flow.
- If no PR exists → ask the user: continue on this branch or create a new one? If continuing, skip to "New PR flow" Step 3 (no branch creation needed).
New PR flow
If the current branch IS the base branch → proceed with full "New PR" flow (including branch creation).
New PR flow
Step 1: Analyze changes
git status
git diff HEAD
git log --oneline -5
If the diff is large, delegate the read instead of pulling it all inline — see Delegating to subagents. Spawn a read-only subagent to summarize git diff HEAD and return the commit type, PR summary bullets, and test plan, then reuse that summary in Step 3 and Step 7.
Step 2: Check for sensitive files
Before staging, scan git status output for potentially sensitive files:
.env,.env.*files- Files containing
secret,credential,token,password,keyin their name id_rsa,*.pem,*.key,*.p12,*.pfx,*.p8,*.keystorefiles*.jsonfiles that look like service account keys (e.g.,*-credentials.json,serviceaccount*.json)
If any are detected, warn the user explicitly and ask whether to exclude them. Do NOT silently stage sensitive files.
Step 3: Propose commit message
Write a conventional commit message based on the diff:
- Format:
<type>: <short summary> - Types:
feat,fix,refactor,docs,style,chore,test,ci - Summary: imperative mood, lowercase, no period, max 72 chars
- Add a body (blank line + wrapped paragraphs) for complex changes
Present the proposed message to the user:
- Accept — use as-is
- Edit — user provides their own
Step 4: Create branch
Derive a branch name from the commit message:
- Take the summary part (after
type:ortype(scope):) - Lowercase, replace spaces with hyphens, remove special characters (including parentheses from scoped types)
- Prefix with the type:
feat/add-dark-mode,fix/null-token-settings - Truncate to 60 characters max
Example: feat(auth): implement JWT tokens → feat/implement-jwt-tokens
git checkout -b <branch-name>
If the branch already exists, append a numeric suffix (e.g., -2).
Step 5: Stage and commit
git add -A
git commit -m "$(cat <<'EOF'
<type>: <summary>
<optional body>
EOF
)"
If the commit fails due to a pre-commit hook, read the hook output, fix the issue, re-stage, and create a NEW commit (do not amend).
Step 6: Push
git push -u origin <branch-name>
If push fails due to remote rejection, report the error and stop — do not force-push.
Step 7: Build PR body
Check if the repository has a PR template:
cat .github/PULL_REQUEST_TEMPLATE.md 2>/dev/null || \
cat .github/pull_request_template.md 2>/dev/null || \
cat PULL_REQUEST_TEMPLATE.md 2>/dev/null || \
cat docs/PULL_REQUEST_TEMPLATE.md 2>/dev/null
If a template exists, use its structure and fill in the sections from the diff context. If no template exists, use this default structure:
## Summary
<1-3 bullet points describing the actual changes from the diff>
## Test plan
<bulleted checklist of testing steps>
Issue linking
If $ARGUMENTS contains --issue <number>, or the user mentions an issue number, or the branch name contains an issue number:
- Fetch the issue details:
gh issue view <number> --json title,body,labels
- Add a closing keyword to the PR body at the end of the Summary section:
## Summary
- <change description>
- <change description>
Closes #<number>
Valid closing keywords (all work the same): Closes, Fixes, Resolves. Use Closes by default. For bug fixes (commit type fix), use Fixes instead.
- If the issue has labels, apply matching labels to the PR after creation:
gh pr edit <pr-number> --add-label "<label1>,<label2>"
Step 8: Create PR
gh pr create --title "<summary>" --base "<base-branch>" --body "$(cat <<'EOF'
<PR body from Step 7>
EOF
)"
- Title: first line of commit summary without the
type:prefix, max 70 chars - Base branch: detected in pre-flight or from
$ARGUMENTS
Step 9: Watch CI
After the PR is created, wait for the GitHub Actions run triggered by the push to finish:
gh run watch $(gh run list --branch <branch-name> --limit 1 --json databaseId --jq '.[0].databaseId') --exit-status
--exit-statusmakes the command exit non-zero when the run fails, so failure is easy to detect.- If no run is found yet, retry after a short delay (CI may not have registered the push immediately).
- If the run succeeds → continue to Step 10.
- If the run fails → when the logs are long, first delegate root-causing to a read-only subagent (see Delegating to subagents) and pass its conclusion — failing step, error, and
file:line— into thegithub-fix-action-errorskill; otherwise invoke that skill directly. After the fix is committed and pushed, re-watch the new run. Repeat until the build is green or the user aborts.
Step 10: Merge the PR
CI is green — the user's intent in invoking this skill is to land the change, so proceed to merge unless --no-merge was passed in $ARGUMENTS. If --no-merge is set, skip to Step 11.
Merging is a shared-state action visible to collaborators, and it's effectively irreversible (revert PRs are possible but messy), so confirm with the user once before doing it — keep the prompt short, since they already opted in by invoking this skill.
Step 10a: Verify mergeability
gh pr view --json number,mergeable,mergeStateStatus,reviewDecision
Interpret the result:
mergeable: MERGEABLEandmergeStateStatus: CLEAN→ ready to mergemergeStateStatus: HAS_HOOKS→ ready (post-merge hooks will run, that's fine)mergeStateStatus: BLOCKED→ branch protection blocks the merge (e.g., required reviewers, required signed commits, code owner review). Report which gate is blocking and stop — do not bypass with--adminunless the user explicitly asksmergeStateStatus: BEHIND→ base branch moved forward and the repo requires an up-to-date branch. Offergh pr update-branch <number>and re-watch CI afterwardmergeable: CONFLICTING→ conflicts with the base branch. Stop and ask the user to resolve manuallymergeStateStatus: UNSTABLE→ required checks haven't completed even though our watched run passed. Investigate which check is pending before mergingreviewDecision: CHANGES_REQUESTED→ at least one reviewer has requested changes. Stop and let the user address the review
Step 10b: Choose a merge strategy
Read the repo's allowed strategies so the chosen flag will actually work:
gh repo view --json mergeCommitAllowed,squashMergeAllowed,rebaseMergeAllowed
Preference order: --squash (cleanest history for feature PRs), then --merge, then --rebase. Pick the first one that's allowed. The skill exists to land PRs cleanly, so squash is the right default — but never pick a strategy the repo doesn't permit, or gh pr merge will reject it.
Step 10c: Confirm and merge
Tell the user concisely what you're about to do and ask once for confirmation:
"CI is green. Merge PR # with
--squashand delete the branch? (y/n)"
If the user declines, skip to Step 11 and just report. If they confirm:
gh pr merge <number> --<strategy> --delete-branch
Notes:
--delete-branchremoves both the local and remote branch after merge — this is the usual cleanup, but skip the flag if the user objects.- If the merge command fails because of branch protection (e.g., 405 method not allowed, "Pull Request is not mergeable"), surface the error and stop. Do not retry with
--adminunless explicitly asked. - If
gh pr mergesucceeds, the local branch is gone; subsequentgitcommands should not assume it still exists. Switch back to the base branch and pull:
git checkout <base-branch>
git pull
Step 11: Report
Output the PR URL, the final CI status, and the merge outcome (merged via squash / merge skipped per --no-merge / merge blocked by ...).
Push to existing PR flow
When a PR already exists for the current branch:
Step 1: Analyze new changes
git status
git diff HEAD
Step 2: Check for sensitive files
Same check as New PR flow Step 2.
Step 3: Propose commit message
Same conventions as New PR flow Step 3.
Step 4: Stage, commit, and push
git add -A
git commit -m "$(cat <<'EOF'
<type>: <summary>
EOF
)"
git push
If the commit fails due to a pre-commit hook, fix the issue and create a NEW commit (do not amend).
If push fails because the remote has new commits, pull with rebase first:
git pull --rebase origin <branch-name>
git push
If rebase has conflicts, stop and report — do not force-push or auto-resolve.
Step 5: Watch CI
After pushing, wait for the GitHub Actions run triggered by the new commit to finish:
gh run watch $(gh run list --branch "$(git branch --show-current)" --limit 1 --json databaseId --jq '.[0].databaseId') --exit-status
--exit-statusmakes the command exit non-zero when the run fails.- If the run succeeds → continue to Step 6.
- If the run fails → when the logs are long, first delegate root-causing to a read-only subagent (see Delegating to subagents) and pass its conclusion — failing step, error, and
file:line— into thegithub-fix-action-errorskill; otherwise invoke that skill directly. After the fix is committed and pushed, re-watch the new run. Repeat until the build is green or the user aborts.
Step 6: Merge the PR
Same flow as New PR flow → Step 10. Skip this step if --no-merge was passed in $ARGUMENTS.
- Verify mergeability with
gh pr view --json mergeable,mergeStateStatus,reviewDecision— only proceed when the PR isMERGEABLEandCLEAN(orHAS_HOOKS). Stop onBLOCKED,BEHIND,CONFLICTING,UNSTABLE, orCHANGES_REQUESTED. - Pick a strategy allowed by the repo (
gh repo view --json mergeCommitAllowed,squashMergeAllowed,rebaseMergeAllowed). Prefer--squash→--merge→--rebase. - Ask the user once: "CI is green. Merge PR # with
--squashand delete the branch? (y/n)" - On confirmation:
gh pr merge <number> --<strategy> --delete-branch, thengit checkout <base-branch> && git pull. - If branch protection blocks the merge, report the gate and stop — do not use
--adminunless the user explicitly asks.
Step 7: Report
Show the existing PR URL, the new commit summary, the final CI status, and the merge outcome:
Pushed to PR #<number>: <pr-title>
New commit: <type>: <summary>
URL: <pr-url>
CI: <success|fixed after N attempts>
Merge: <merged via squash|skipped per --no-merge|blocked by required reviewers|declined by user>
Do NOT create a new PR — just push to the existing one. Do NOT modify the PR title or body.
Error handling
| Scenario | Detection | Action |
|---|---|---|
gh not installed |
command -v gh fails |
Direct user to https://cli.github.com |
| Not in a git repo | git rev-parse --show-toplevel fails |
Abort with clear message |
| No changes to commit | git status shows clean tree |
Abort unless PR from existing commits |
| Sensitive files detected | Pattern match on git status output |
Warn user, ask to exclude before staging |
| Pre-commit hook failure | git commit exits non-zero |
Read output, fix issue, create new commit |
| Push rejected | git push exits non-zero |
Report error, do not force-push |
| Branch already exists | git checkout -b fails |
Append numeric suffix (e.g., -2) |
| PR creation fails | gh pr create exits non-zero |
Report error (branch protection, permissions) |
| Remote ahead (push fails) | git push rejected, non-fast-forward |
git pull --rebase, then retry push |
| Rebase conflicts | git pull --rebase has conflicts |
Stop and report, do not auto-resolve |
| Merge blocked by branch protection | mergeStateStatus: BLOCKED, or gh pr merge exits non-zero with 405 / "not mergeable" |
Report which gate is blocking (required reviewers, code owner, signed commits, etc.) and stop — do not bypass with --admin unless asked |
| Branch is BEHIND base | mergeStateStatus: BEHIND |
Offer gh pr update-branch <number>, re-watch CI, then retry merge |
| Merge conflicts with base | mergeable: CONFLICTING |
Stop and ask user to resolve manually |
| Changes requested on PR | reviewDecision: CHANGES_REQUESTED |
Stop and let the user address the review before merging |
| Required check still pending | mergeStateStatus: UNSTABLE after our watched run passed |
Investigate the pending check (likely a separate required workflow); do not merge until it lands |
| User declines merge confirmation | User answers "n" to merge prompt | Skip merge, report PR URL and CI status, exit cleanly |
Constraints
These boundaries protect the user's repository and team workflow:
- Never commit directly to the base branch (e.g.
main) — always land work through the full flow: create a feature branch first, then commit → push → open a PR → watch CI to green → squash-merge → delete the branch. The base branch is shared and usually protected; committing straight to it skips review and CI, the very gates this skill exists to enforce. When pre-flight detects the current branch IS the base branch, create the feature branch (New PR flow Step 4) before staging anything. - Do not force-push — it rewrites shared history and can cause data loss for collaborators
- Do not amend existing commits — create new ones instead, so the commit timeline stays transparent and reviewable
- Do not skip pre-commit hooks (
--no-verify) — hooks enforce project-level quality gates that exist for a reason - Do not commit
.env, credentials, or secrets — always run the sensitive file check before staging and warn if anything is detected - If any step fails, stop and report the error — do not continue blindly, because later steps depend on earlier ones succeeding
- Use
git add -Afor staging (full-change commit flow), but only after the sensitive file check passes - The PR body must reflect the actual changes from the diff, not boilerplate — reviewers rely on it to understand the change
- Issue closing keywords (
Closes #N) go in the PR body, not in the commit message — GitHub only processes closing keywords from the PR body on the default branch - When pushing to an existing PR, do not modify the PR title or body — only push the new commit
- Do not merge a PR until the watched GitHub Actions run has actually finished green —
gh run watch --exit-statusis the gate, never trust an in-flight or pending status - Always confirm with the user once before merging — merging is shared state, visible to collaborators, and reverts are messy; the confirmation is the user's final chance to pause
- Do not use
gh pr merge --adminto bypass branch protection unless the user explicitly asks — protection rules exist to enforce review and quality gates, and bypassing them silently undermines the team's process - Respect
--no-mergein$ARGUMENTS— when set, push and report but never callgh pr merge, so the user can hand the PR off for human review