-
Determine push access and the fork question.
gh auth status
git -C <repo> remote -v
- If
origin is owned by the authenticated user → push directly
to a branch, no fork needed.
- Otherwise → fork first:
gh repo fork --remote=true.
-
Create a descriptive branch, commit, push.
git -C <repo> checkout -b <slug>
git -C <repo> add <paths>
git -C <repo> commit -F .commit_msg.tmp
git -C <repo> push -u origin <slug>
-
Check the default branch, don't assume main:
git -C <repo> symbolic-ref refs/remotes/origin/HEAD | sed 's@^.*/@@'
-
Create the PR with a body file (see related skill):
gh pr create \
--repo <owner>/<repo> \
--base <default-branch> \
--head <slug> \
--title "<one-line title>" \
--body-file pr_body.md
gh prints the PR URL on success. Capture it.
-
Anti-pattern — never do this:
gh pr create --body "$(cat <<'EOF'
… multi-line body with ``` fences, $(…), backslashes …
EOF
)"
This triple-nests double quotes, command substitution, and a
heredoc. In zsh (the default agent shell on macOS) any backtick
fence or $(…) inside the body will unbalance the parser and the
command hangs at cmdand dquote> / cmdand quote> — the agent
sees "command interrupted" and the PR is never filed. Always
write the body to a file via the editor tool and pass
--body-file <path>. See
shell-heredoc-and-multiline-strings.
-
--title with backticks or $ through the shell is still fragile;
prefer --body-file and keep the title short enough to put in a
single-quoted string.
-
--body "…\n…" does NOT give you line breaks. gh takes the
value verbatim; \n stays as a two-character literal and the PR
body on GitHub will show \n in the middle of sentences. Always
use --body-file <path> for anything longer than one line (see
shell-heredoc-and-multiline-strings).
-
Very long gh pr create invocations get backgrounded by some
agent shells — the command appears to "hang" and the PR URL never
comes back. Keep the command short (use --body-file instead of a
giant inline --body), and verify with
gh pr list --repo <owner>/<repo> --state open afterwards.
-
If you already created the PR and the body is malformed (e.g.
literal \n showing up), fix it without a new PR:
gh pr edit <N> --repo <owner>/<repo> --body-file pr_body.md
-
If gh pr create complains about "no commits between base and head",
you forgot to push the branch first, or you branched off the wrong
base — see
rebase-on-fresh-base-after-merge.
-
If the target repo has branch protection or required checks, the
command still succeeds — the PR is created but will sit in a
pending state. Mention this to the user if relevant.
-
gh pr view <N> --json mergeable,mergeStateStatus can return
"UNKNOWN" / "UNKNOWN" right after another PR lands on the same
base. GitHub computes mergeability asynchronously; the value is
not populated instantly. If you need a decision (e.g. "does this
PR still merge cleanly after the one we just merged?"), wait a
few seconds and re-query:
gh pr view <N> --json mergeable,mergeStateStatus
sleep 5
gh pr view <N> --json mergeable,mergeStateStatus
Don't interpret a single UNKNOWN as "there's a conflict" — it
just means GitHub hasn't finished the background check yet. The
gh pr checks and the web UI exhibit the same delay.
-
gh run rerun --job <id> wants the display name upstream, not
the YAML job key. When you select a specific workflow job to
rerun (or view), --job takes the job's numeric databaseId and
that ID is resolved against the display name shown in the
GitHub Checks UI — which is the name: field from the workflow
YAML, not the jobs.<key> identifier above it. Pattern-matching
the YAML key will silently miss:
jobs:
e2e-smoke:
name: E2E smoke (blade-test)
JOB_ID=$(gh run view <run> --json jobs \
| jq -r '.jobs[] | select(.name=="e2e-smoke") | .databaseId')
gh run rerun <run> --job "$JOB_ID"
gh run view <run> --json jobs \
| jq -r '.jobs[] | "\(.databaseId) \(.name) \(.conclusion)"'
gh run rerun <run> --job 73167567958
The same applies to gh run view <run> --log --job <id> when you
want to grep a specific job's log. Always list .jobs[].name
first and copy the exact string; never assume it equals the YAML
key.