GitHub CLI, gh Authentication, Repository Operations, and Scripting Workflows: Guided Hands-On Workflow and Core Operations
Now turn the model into evidence. You will work only in a disposable public repository, inspect authentication before mutation, create traceable work items, prove state with JSON, and compare a first-class command with the underlying REST surface.
Learning objectives
- Verify a current GitHub CLI installation and authenticated host/account without revealing a token.
- Create a disposable public repository and prove repository/branch identity before changing hosted work items.
- Create, list, view, and edit disposable Issues and create one small pull request using explicit repository context.
-
Compare first-class
gh issue listJSON with a versioned, paginated read-only REST request. - Write a small script that selects repository context explicitly and returns a non-zero status when a prerequisite fails.
1. Lab setup and preflight
This lab uses one disposable public personal repository. GitHub Free is sufficient. You need local Git and the GitHub CLI. Do not use an employer repository, organization policy sandbox, or any credential you are unwilling to revoke.
Install or upgrade GitHub CLI from the current official installation guidance for your operating system, then prove what binary is on your path:
gh --version
gh help
gh auth status --active --hostname github.com
If authentication is missing, the preferred human setup is the
browser flow:
gh auth login --hostname github.com --web. For headless
automation, GitHub documents environment-token authentication as the
appropriate model. Do not paste a token into lesson notes, shell
history, or chat transcripts.
gh auth login --with-token accepts a classic PAT from
standard input and documents minimum classic scopes of
repo, read:org, and gist. For
fine-grained PAT use, GitHub recommends providing it through
GH_TOKEN. This lab does not require creating either
kind of PAT.
2. Create the disposable repository and prove identity
Use a unique name. The Bash/Git Bash commands below create a repository with one README commit, then record the exact hosted identity before any Issue or PR work.
OWNER="$(gh api user -H "X-GitHub-Api-Version: 2026-03-10" --jq .login)"
LAB="gh-cli-lab-$(date +%Y%m%d-%H%M%S)"
REPO="$OWNER/$LAB"
gh repo create "$REPO" --public --add-readme --description "Disposable GitHub CLI automation lab"
gh repo view "$REPO" \
--json nameWithOwner,visibility,defaultBranchRef,url,viewerPermission \
--jq '{repo:.nameWithOwner, visibility, defaultBranch:.defaultBranchRef.name, permission:.viewerPermission, url}'
$OWNER = gh api user -H "X-GitHub-Api-Version: 2026-03-10" --jq
.login,
$LAB =
"gh-cli-lab-$((Get-Date).ToString('yyyyMMdd-HHmmss'))", and $REPO = "$OWNER/$LAB". The subsequent
gh commands are identical.
Expected: the repository is public, the default branch is normally
main for a newly initialized repository under common
account settings, and your viewer permission reflects ownership. Do
not hard-code main in automation—capture the observed
default branch field.
3. Create, list, view, and edit Issues with structured evidence
Issue creation is a GitHub-hosted mutation; it does not create a Git commit. Make the mutation explicit by supplying every required field and repository instead of allowing a prompt.
ISSUE_URL="$(gh issue create -R "$REPO" \
--title "CLI lab: report should expose repository identity" \
--body "Disposable work item for Chapter 12. Safe to close during cleanup.")"
ISSUE_NUMBER="${ISSUE_URL##*/}"
printf 'created issue #%s\n' "$ISSUE_NUMBER"
gh issue list -R "$REPO" --state open \
--json number,title,state,updatedAt,url \
--jq '.[] | {number,title,state,updatedAt,url}'
gh issue view "$ISSUE_NUMBER" -R "$REPO" \
--json number,title,body,state,url
gh issue edit "$ISSUE_NUMBER" -R "$REPO" \
--title "CLI lab: explicit repository report context" \
--body "Updated non-interactively by gh. Disposable Chapter 12 evidence."
gh issue view "$ISSUE_NUMBER" -R "$REPO" \
--json number,title,updatedAt,url
The before/after evidence should show the same Issue number and URL with a changed title/body/update timestamp. There is no Git SHA because Issue metadata is not a Git object.
4. Add a small pull request so the report has both work-item types
The checkpoint report later lists open pull requests. Create one tiny branch and PR now. This reuses Chapters 7–9 without reteaching PR mechanics.
gh repo clone "$REPO" "$LAB-work"
cd "$LAB-work"
DEFAULT_BRANCH="$(gh repo view "$REPO" --json defaultBranchRef --jq .defaultBranchRef.name)"
git switch -c cli-report-sample "$DEFAULT_BRANCH"
printf 'sample input for repository report\n' > report-sample.txt
git add report-sample.txt
git commit -m "docs: add report sample"
git push -u origin cli-report-sample
PR_URL="$(gh pr create -R "$REPO" \
--base "$DEFAULT_BRANCH" \
--head cli-report-sample \
--title "CLI lab: sample open change" \
--body "Disposable PR used by the repository-report script.")"
HEAD_SHA="$(git rev-parse HEAD)"
gh pr view "$PR_URL" --json number,state,isDraft,headRefName,headRefOid,baseRefName,url
Verify that headRefOid equals HEAD_SHA.
That is a cross-layer proof: local Git commit identity agrees with
the GitHub pull-request head ref.
5. Write a small explicit-context script with predictable failure
The script below is intentionally simple. It takes the repository as an argument, disables prompting, checks authentication, and refuses to proceed if repository inspection fails. It does not rely on the current directory.
#!/usr/bin/env bash
set -u
REPO="${1:-}"
HOST="${2:-github.com}"
if [[ -z "$REPO" ]]; then
echo "usage: repo-summary.sh OWNER/REPO [HOST]" >&2
exit 64
fi
export GH_PROMPT_DISABLED=1
export GH_HOST="$HOST"
if ! gh auth status --active --hostname "$HOST" >/dev/null 2>&1; then
echo "authentication preflight failed for $HOST" >&2
exit 65
fi
if ! gh repo view "$REPO" --json nameWithOwner,visibility,defaultBranchRef,url; then
echo "repository lookup failed: $HOST/$REPO" >&2
exit 66
fi
Save this as repo-summary.sh, make it executable in
Bash/Git Bash, and run it with the exact OWNER/REPO.
Then intentionally pass
OWNER/definitely-not-this-repository. Expected: the
script exits 66 and does not mutate GitHub.
6. Compare a first-class command with versioned REST pagination
First-class Issue listing expresses intent clearly. The REST version exposes raw API semantics and is useful when you need fields or behavior not surfaced by the first-class command. Run both and compare the Issue number/title.
# First-class surface
gh issue list -R "$REPO" --state open --limit 100 \
--json number,title,state,url \
--jq '.[] | {number,title,state,url}'
# Raw REST surface. The REST Issues endpoint can also return pull requests,
# so filter objects that contain pull_request when you mean Issues only.
gh api --paginate \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2026-03-10" \
"repos/$REPO/issues?state=open&per_page=2" \
--jq '.[] | select(.pull_request == null) | {number,title,state,html_url}'
Why per_page=2? The lab intentionally uses a small page
size so --paginate is observable if you create
additional test Issues. Production scripts should choose a sensible
page size and still handle pagination rather than assuming the first
page is complete.
Rate limits are part of the API contract. For larger automation, inspect response/rate-limit state, cache safe reads where appropriate, and do not retry mutations blindly.
7. Challenge: choose the correct surface
For each requirement, choose the narrowest reliable surface and justify it before executing anything:
| Requirement | Best starting surface | Reason to defend |
|---|---|---|
| List open Issues with number/title for a report | gh issue list --json |
First-class semantics and stable documented fields. |
| Read a newly introduced REST-only repository field | gh api GET |
No first-class field yet; explicit version/header and response handling required. |
| Create 500 linked resources across services with complex retry/idempotency logic | SDK or purpose-built program | Typed models, tests, observability, concurrency/retry control may outweigh shell convenience. |
| Open a PR interactively once | First-class gh pr create |
Workflow convenience is the point; prompts can help a human. |
Do not memorize the table. The decision rule is: prefer the highest-level documented interface that fully expresses the operation and still provides the data/error contract your automation needs.
8. Preserve evidence; defer cleanup to the checkpoint
Keep the repository, Issue, branch, and PR open for Lessons 3–5.
Record REPO, ISSUE_NUMBER, PR number/URL,
DEFAULT_BRANCH, and HEAD_SHA in a local
lab note that contains no credentials. The final checkpoint will
close disposable work and archive the repository.
9. Lesson summary
The guided lab connected one disposable repository to explicit
gh context, Issue and PR mutations, local Git commit
identity, structured JSON evidence, a wrapper exit-code contract,
and a versioned paginated REST read. The important skill is not
command memorization; it is proving exactly which hosted object
changed and why.
Knowledge check
After gh issue edit, should the repository Git
commit SHA change?
No. Issue metadata is hosted GitHub state, not Git history. A separate Git commit/push is required to change repository content.
Why does the raw REST Issues list need to filter objects with a
pull_request field?
GitHub’s REST Issues endpoint can include pull requests because
PRs share parts of the Issues data model. The first-class
gh issue list already expresses Issue-only intent.
What does matching local HEAD_SHA to the PR
headRefOid prove?
It independently connects the local Git commit to the hosted pull-request head ref, demonstrating causality across Git and GitHub layers.
A wrapper maps repository lookup failure to exit 66. Why keep the original gh stderr too?
The wrapper code gives automation a stable application contract, while original stderr preserves the GitHub/CLI cause needed by an operator.
Further reading — current official GitHub sources
- GitHub CLI manual — environment variables
- GitHub CLI manual — authentication status
- GitHub CLI manual — authentication login
- GitHub CLI manual — formatting
- GitHub CLI manual — exit codes
- GitHub CLI manual — gh api
- GitHub CLI manual — configuration
- GitHub CLI manual — aliases
- GitHub CLI manual — extensions
- GitHub REST API — API versions
- GitHub REST API — rate limits
- GitHub CLI repository — installation guidance
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.