본문으로 건너뛰기
cccskills
무료GitHub에 공개

github-pr-workflow

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

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

연동 도구GitHub
설치 방법 보기

포함된 파일(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.

리뷰

아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.

댓글

로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

보내면 바로 공개됩니다. 이메일 주소나 전화번호 같은 개인 정보는 쓰지 마세요. URL은 2개까지입니다.

0 / 1,000자

이 양식은 reCAPTCHA로 보호되며 Google 개인정보처리방침 및 서비스 약관이 적용됩니다.

같은 저장소의 스킬

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의 스킬 모두 보기

이 스킬의 문제 신고