本文へ移動
cccskills
無料GitHub で公開

github-pr-workflow

Take a change from branch to merged pull request, CI included.

インストール方法を見る

含まれるファイル(5)

  • SKILL.md10.2 KB
  • references/ci-troubleshooting.md4.8 KB
  • references/conventional-commits.md2.3 KB
  • templates/pr-body-bugfix.md511 B
  • templates/pr-body-feature.md580 B

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

GitHub pull request workflow

Branch, commit, open the pull request, watch CI, fix what it says, merge, clean up. Every step is given twice: with gh, and with git plus curl for the machines that do not have it, which is most servers.

Run the shell blocks through shell_exec. Read github-auth first if anything returns 401 or 404.

Two things that need consent

Pushing and merging are outward actions. A push puts the user's name on a public commit; a merge changes the default branch other people build on. Do them because the user asked for them, not because the workflow reached that step. When in doubt, stop and show what you are about to push.

Never force-push a branch that has left the machine, and never rewrite history on a shared branch, on your own initiative.

Setup, once per session

if command -v gh >/dev/null 2>&1 && gh auth status >/dev/null 2>&1; then
  AUTH=gh
else
  AUTH=curl   # ${GITHUB_TOKEN} comes from the LaRuche vault
fi

OWNER_REPO=$(git remote get-url origin | sed -E 's|.*github\.com[:/]||; s|\.git$||')
OWNER=${OWNER_REPO%%/*}
REPO=${OWNER_REPO##*/}
echo "$AUTH on $OWNER/$REPO"

If OWNER_REPO comes out empty or still looks like a URL, the remote is not GitHub, or there is no origin. Stop there: everything below will fail in a way that blames authentication.

1. Branch

git fetch origin
git switch main && git pull --ff-only origin main
git switch -c feat/jwt-authentication

--ff-only refuses to create a merge commit behind your back. If it fails, the local main has commits that are not on the remote, and that is worth understanding before branching off it.

Prefix by intent: feat/, fix/, refactor/, docs/, ci/, chore/.

2. Commit

Edit with file_write and file_edit, then stage deliberately:

git status
git add src/auth.py src/models/user.py tests/test_auth.py
git commit -m "feat: add JWT-based authentication

Sessions were stored server-side, which blocked horizontal scaling.
Tokens move that state to the client.

Closes #42"

Name the files. git add . sweeps in build output, editor droppings and, once in a while, a .env. Read git status before staging, every time.

The subject line follows Conventional Commits, type(scope): summary, under about 72 characters, imperative. The body says WHY: the diff already says what.

3. Open the pull request

git push -u origin HEAD

With gh:

gh pr create --title "feat: add JWT-based authentication" --body "$(cat <<'BODY'
## What this changes
Session state moves from the server to a signed token.

## Why
Server-side sessions pinned every user to one process, which blocked scaling out.

## How to verify
- `pytest tests/test_auth.py -q`
- log in, restart the server, confirm the session survives

Closes #42
BODY
)"

--draft opens it unreviewable, --reviewer a,b, --label enhancement, --base develop when the target is not the default branch.

With curl:

BRANCH=$(git branch --show-current)
curl -s -X POST \
  -H "Authorization: Bearer ${GITHUB_TOKEN}" \
  -H "Accept: application/vnd.github+json" \
  "https://api.github.com/repos/$OWNER/$REPO/pulls" \
  -d "$(python -c "
import json, sys
print(json.dumps({
  'title': 'feat: add JWT-based authentication',
  'body': open('/tmp/pr-body.md').read(),
  'head': sys.argv[1],
  'base': 'main',
}))" "$BRANCH")"

Build the JSON with a tool, not with string interpolation. A body containing a quote, a backtick or a newline produces malformed JSON, and the API answers with a validation error that says nothing about quoting.

Keep the number from the response: it is PR_NUMBER everywhere below.

4. Watch CI

gh pr checks           # once
gh pr checks --watch   # until everything settles

With curl, and this is where the trap is:

SHA=$(git rev-parse HEAD)
curl -s -H "Authorization: Bearer ${GITHUB_TOKEN}" \
  "https://api.github.com/repos/$OWNER/$REPO/commits/$SHA/check-runs" \
  | python -c "
import sys, json
runs = json.load(sys.stdin).get('check_runs', [])
if not runs:
    print('no check runs')
for run in runs:
    print(f\"{run['name']}: {run['status']} / {run['conclusion'] or 'pending'}\")"

Use check-runs, not status. They are two different APIs. The legacy /commits/$SHA/status endpoint only reports commit statuses posted by external CI. A repository whose CI is GitHub Actions returns "state": "pending" there with an empty statuses array, forever. Polling it waits for something that will never arrive, and the timeout gets read as "CI is slow" rather than "wrong endpoint".

Poll on the conclusion, with a ceiling:

SHA=$(git rev-parse HEAD)
for i in $(seq 1 20); do
  PENDING=$(curl -s -H "Authorization: Bearer ${GITHUB_TOKEN}" \
    "https://api.github.com/repos/$OWNER/$REPO/commits/$SHA/check-runs" \
    | python -c "
import sys, json
runs = json.load(sys.stdin).get('check_runs', [])
print(sum(1 for r in runs if r['status'] != 'completed'))")
  echo "poll $i: $PENDING still running"
  [ "$PENDING" = "0" ] && break
  sleep 30
done

Ten minutes maximum. If it is still running after that, say so rather than looping: some suites take an hour, and a silent agent looks identical to a stuck one.

5. Fix what CI says

gh run list --branch "$(git branch --show-current)" --limit 5
gh run view <RUN_ID> --log-failed

With curl, the logs come back as a redirect to a zip, so -L is not optional:

curl -sL -H "Authorization: Bearer ${GITHUB_TOKEN}" \
  "https://api.github.com/repos/$OWNER/$REPO/actions/runs/$RUN_ID/logs" \
  -o /tmp/ci-logs.zip
unzip -o /tmp/ci-logs.zip -d /tmp/ci-logs && grep -ri "error\|failed" /tmp/ci-logs | head -40

Then: read the real error, reproduce it LOCALLY, fix it, run the local check, push once.

Reproduce locally before pushing a fix. Pushing a guess and waiting for CI is a fifteen-minute compile of a hypothesis you could have tested in ten seconds. If the failure genuinely only happens in CI, that difference is the bug: environment, version, ordering.

Stop after three attempts. Three failed fixes means the diagnosis is wrong, not that the fourth will land. Say what you tried, paste the failing output, and hand it back.

6. Merge

gh pr merge --squash --delete-branch          # now
gh pr merge --auto --squash --delete-branch   # when checks pass

With curl:

curl -s -X PUT -H "Authorization: Bearer ${GITHUB_TOKEN}" \
  "https://api.github.com/repos/$OWNER/$REPO/pulls/$PR_NUMBER/merge" \
  -d '{"merge_method": "squash"}'

merge_method is merge, squash or rebase. Match the repository's habit: read git log --oneline -20 on the default branch. A merge commit in a history of clean squashes is noise somebody has to explain.

Auto-merge has no REST endpoint; it is GraphQL only:

NODE_ID=$(curl -s -H "Authorization: Bearer ${GITHUB_TOKEN}" \
  "https://api.github.com/repos/$OWNER/$REPO/pulls/$PR_NUMBER" \
  | python -c "import sys, json; print(json.load(sys.stdin)['node_id'])")

python - "$NODE_ID" <<'PY' > /tmp/automerge.json
import json, sys
print(json.dumps({
    "query": "mutation($id: ID!) {"
             " enablePullRequestAutoMerge(input: {pullRequestId: $id, mergeMethod: SQUASH})"
             " { clientMutationId } }",
    "variables": {"id": sys.argv[1]},
}))
PY

curl -s -X POST -H "Authorization: Bearer ${GITHUB_TOKEN}" \n  https://api.github.com/graphql --data @/tmp/automerge.json

Then clean up:

git switch main && git pull --ff-only origin main
git branch -d "$BRANCH"

-d refuses to delete a branch that is not merged. Never reach for -D to make that message go away: it is telling you the work is not where you think it is.

Quick reference

ActionghREST
My open PRsgh pr list --author @meGET /repos/O/R/pulls?state=open
Diffgh pr diffgit diff main...HEAD
Commentgh pr comment N --body "..."POST /repos/O/R/issues/N/comments
Request reviewgh pr edit N --add-reviewer uPOST /repos/O/R/pulls/N/requested_reviewers
Closegh pr close NPATCH /repos/O/R/pulls/N {"state":"closed"}
Check out a PRgh pr checkout Ngit fetch origin pull/N/head:pr-N

Note main...HEAD, three dots: that is the diff against the merge base, which is what the pull request shows. Two dots compares the tips and includes everything that landed on main since you branched.

Traps

  • gh pr create before pushing fails with a message about the branch not existing on the remote. Push first.
  • A PR body built by string interpolation breaks on the first quote or backtick. Serialise it with a tool.
  • Branch protection rejects the merge, not the token. Read the message: required reviews and required checks are the usual causes, and neither is fixed by a new token.
  • Squashing loses the co-authors in a multi-author branch unless the trailers are carried into the squash message.
  • A push to a protected default branch is rejected outright. That is the protection working. Open a PR.
  • --delete-branch deletes it locally too, so run it from a different branch or expect to be moved.

Failure modes

gh works and git push asks for a password. gh auth setup-git was never run. See github-auth.

422 Validation Failed on PR creation. Almost always one of: a PR already exists for this head branch, head and base are identical, or the body was malformed JSON. Read the errors array in the response; it names the field.

CI never finishes. You are polling /status on an Actions repository. Use /check-runs. See section 4.

A check is red but the logs look clean. The failing step is a different job. List the runs and open the one whose conclusion is failure, not the most recent one.

Merge returns 405 Method Not Allowed. The PR is not mergeable: conflicts, a required check pending, or a required review missing. gh pr view --json mergeable,mergeStateStatus says which.

The branch will not delete after merging. It was merged by squash, so git cannot see its commits in main. Confirm the PR is merged on GitHub, then delete it with -D, which is the one case where that is correct.

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

Drive an installed LaRuche App through its declared actions, never its files.

日本語の概要は準備中です。原文の説明を表示しています。

infinition/LaRuche332026年9月22日 更新

arxiv

無料

Find academic papers on arXiv, with citation counts and BibTeX.

日本語の概要は準備中です。原文の説明を表示しています。

infinition/LaRuche332026年9月22日 更新

ascii-art

無料

Render text or an image as ASCII art for terminal-friendly output.

日本語の概要は準備中です。原文の説明を表示しています。

infinition/LaRuche332026年9月22日 更新

Track RSS/Atom feeds and blogs via blogwatcher-cli.

日本語の概要は準備中です。原文の説明を表示しています。

infinition/LaRuche332026年9月22日 更新

Drive a real web browser: navigate, read, find, click, fill, screenshot

日本語の概要は準備中です。原文の説明を表示しています。

infinition/LaRuche332026年9月22日 更新

Measure a codebase: lines of code, language mix, and symbol lookups.

日本語の概要は準備中です。原文の説明を表示しています。

infinition/LaRuche332026年9月22日 更新

infinition のスキルをすべて見る

このスキルの問題を報告する