Chapter 12Lesson 02~180 minutes

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.

Disposable labIssues and PRsJSON evidenceREST pagination

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 list JSON 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.
Mandatory lab: GitHub.com, GitHub Free, one disposable public personal repository, local Git, and an authenticated GitHub CLI. No organization, paid plan, self-hosted runner, or new PAT is required.

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.

Mandatory availability: GitHub.com + GitHub Free + a public personal repository. You must be the repository owner (or otherwise have write access) for the Issue/PR mutations. The REST request is read-only.

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.

Credential note: 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}'
PowerShell: $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.

Why map exit codes? GitHub CLI itself uses documented general codes, but your wrapper can translate failures into an application-specific contract. Document the mapping and preserve stderr so operators still see the original cause.

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?

Why does the raw REST Issues list need to filter objects with a pull_request field?

What does matching local HEAD_SHA to the PR headRefOid prove?

A wrapper maps repository lookup failure to exit 66. Why keep the original gh stderr too?

Next lesson

Next: GitHub CLI, gh Authentication, Repository Operations, and Scripting Workflows: Configuration, Design Choices, and Tradeoffs

Further reading — current official GitHub sources

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.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.