Remote repository operations, codebase comparison, and code discovery without cloning.
bash
gh repo read-file path/file.ts --repo OWNER/REPO
gh repo read-file (preview) is the preferred path: it prints raw content, takes --ref for any branch/tag/commit, and handles large files. --output PATH writes to disk ( --clobber to overwrite); --json returns metadata ( content, downloadUrl, encoding, gitSHA, gitUrl, htmlUrl, name, path, size, type, url). Fall back to gh api where the command is unavailable:
bash
gh api repos/OWNER/REPO/contents/path/file.ts -H "Accept: application/vnd.github.raw"
There is no base64decode template function - --template '{{.content | base64decode}}' fails with function "base64decode" not defined. To decode the default JSON response, pipe it:
bash
gh api repos/OWNER/REPO/contents/path/file.ts --jq '.content' | base64 -d
The Contents API inlines only files up to 1MB. Past that, .content is an empty string ( encoding: "none") and the pipe above silently prints nothing - use read-file or the raw Accept header instead.
gh refuses to print remote content containing terminal escape sequences (ANSI art, some test fixtures) instead of passing it to your terminal:
gh repo read-file: file contains terminal escape sequences; use --allow-escape-sequences to read anywaygh api raw bodies: the response contains terminal escape sequences; pass --allow-escape-sequences to output it anywaygh pr diff when piped: the diff contains terminal escape sequences; pass --allow-escape-sequences to output it anywayPass --allow-escape-sequences (also on gh release download --output - and gh gist view) when you need the bytes verbatim, or write to disk with gh repo read-file --output, which always keeps the raw bytes. The check came with the GHSA-3m3g-3wcr-px46 fix; run gh 2.98.0 or later to get it and the other 2.97/2.98 security fixes.
bash
gh repo read-dir PATH --repo OWNER/REPO # omit PATH to list the repo root
# Or via the API
gh api repos/OWNER/REPO/contents/PATH
gh repo read-dir --json returns an object, not an array - {"entries":[...]} - so filter with .entries[]:
bash
gh repo read-dir src --repo OWNER/REPO --json name,type --jq '.entries[] | select(.type=="file") | .name'
gh infers the repository from the current working directory. In agent or CI workflows - where a cd may persist - always pass --repo OWNER/REPO so a stray cwd cannot silently retarget the command. Signatures of a missing --repo:
failed to run git: fatal: not a git repositoryno git remotes foundbash
gh search code "pattern" --language=typescript
bash
gh search repos --language=rust --sort stars --order desc
Systematic workflow for comparing repositories to identify similarities and differences.
Example use: "Compare solana-fm/explorer-kit and tenequm/solana-idls"
bash
gh repo read-dir PATH --repo OWNER-A/REPO-A
gh repo read-dir PATH --repo OWNER-B/REPO-B
If comparing a monorepo package, specify the path (e.g., packages/explorerkit-idls).
bash
diff <(gh repo read-dir PATH --repo OWNER-A/REPO-A --json name --jq '.entries[].name') \
<(gh repo read-dir PATH --repo OWNER-B/REPO-B --json name --jq '.entries[].name')
Lines prefixed < exist only in repo A, > only in repo B.
Compare package dependencies:
bash
gh repo read-file package.json --repo OWNER-A/REPO-A
gh repo read-file package.json --repo OWNER-B/REPO-B
Compare main entry points:
bash
gh repo read-file src/index.ts --repo OWNER-A/REPO-A
gh repo read-file src/index.ts --repo OWNER-B/REPO-B
Add --cache 1h to gh api calls when iterating on the same files repeatedly, to avoid re-spending rate limit.
Comparing two refs of the same repo (or a fork of it) is one call to the compare API, capped at 250 commits and 300 files:
bash
gh api repos/OWNER/REPO/compare/v1.0.0...v2.0.0 --jq '{ahead_by, files: [.files[].filename]}'
Compare the fetched files to identify:
API Surface
Dependencies
Unique Features
For detailed comparison strategies, see references/comparison.md.
bash
# Most starred repos - a bare `--sort` with no query or filter flag fails: Invalid search query ""
gh search repos "stars:>10000" --sort stars --order desc --limit 20
# Trending in specific language
gh search repos --language=rust --sort stars --order desc
# Recently popular (created in the last 30 days; works with BSD and GNU date)
SINCE=$(date -v-30d +%F 2>/dev/null || date -d '30 days ago' +%F)
gh search repos "created:>$SINCE" --sort stars --order desc
# Trending in specific topic
gh search repos "topic:machine-learning" --sort stars --order desc
bash
# Find popular implementations (code search has no sorting - scope with filters)
gh search code "function useWallet" --language=typescript
# Scope to a known repo (code search can't filter by stars - stars:>N is literal text)
gh search code "implementation" --repo=honojs/hono
# Search specific organization
gh search code "authentication" --owner=anthropics
For complete discovery queries and patterns, see references/discovery.md.
bash
# Search across all repositories
gh search code "API endpoint" --language=python
# Search in specific organization
gh search code "auth" --owner=anthropics
# Exclude results with negative qualifiers
gh search issues -- "bug report -label:wontfix"
bash
# Find open bugs (--state takes only open|closed here; omit it for both)
gh search issues --label=bug --state=open
# Search assigned issues
gh search issues --assignee=@me --state=open
# Meaning-based issue search (gh 2.98.0+): relevance-ranked, issues only, one page, no --sort/--order
gh search issues worktree checkout --repo cli/cli --search-type hybrid
Separate words are ANDed. A single quoted multi-word argument is sent as an exact phrase, so gh search issues "worktree checkout" can return nothing with exit 0.
Search runs on a much tighter budget than the 5000/hr core API - check with gh api rate_limit:
ResourceLimit (authenticated)core (incl. gh api, gh repo read-file)5000/hrsearch (repos, issues, prs, commits)30/mincode_search10/minsemantic_search ( --search-type semantic|hybrid)10/min - not listed by gh api rate_limit
For advanced search syntax, see references/search.md.
IMPORTANT: GitHub CLI uses inconsistent field names across commands:
Fieldgh repo view``gh search reposStarsstargazerCount``stargazersCountForksforkCount``forksCountTopicsrepositoryTopicsnot availableWatcherswatchers ( {totalCount})watchersCount - actually the star count
Examples:
bash
# ✅ Correct for gh repo view
gh repo view owner/repo --json stargazerCount,forkCount
# ✅ Correct for gh search repos
gh search repos "query" --json stargazersCount,forksCount
Other fields that do not exist where you expect them: gh pr view has no merged (use mergedAt or state), gh search prs has no mergedAt (use closedAt), and gh run list has no jobs (use gh run view <id> --json jobs). A wrong field fails with Unknown JSON field: "X" followed by the valid list.
gh search (gh 2.97.0+) splits each argument at its first : and wraps the rest in quotes if it contains a space. Packing several qualifiers into one quoted string silently breaks the filter:
bash
# ❌ Sent as language:"go stars:>500" - the language filter is lost
gh search repos "rate limiting middleware language:go stars:>500"
# ✅ Keywords and qualifiers as separate arguments; quote only those containing > or <
gh search repos rate limiting middleware language:go "stars:>500"
gh pr list --search and gh issue list --search pass their string through untouched, so one quoted string is fine there.
A negative qualifier works as its own argument or at the end of a quoted string ( "bug report -label:wontfix"). -- is only required when the query starts with a hyphen, which the shell would otherwise read as a flag.
Put every flag before --. Everything after -- is positional, so trailing flags get swallowed into the query string:
bash
# ✅ Correct - flags first
gh search issues --limit 5 -- -label:wontfix bug
# ❌ Wrong - --limit 5 becomes part of the search query, silently returning 30 results
gh search issues -- -label:wontfix bug --limit 5
For more syntax gotchas, see references/syntax.md.
Every command needs auth - without a token gh prints please run: gh auth login and exits 4. The exception is gh release download on public repos.
gh api writes its error body to stdout. On failure it exits 1, prints gh: Not Found (HTTP 404) to stderr, and prints the error JSON to stdout without applying --jq. $(gh api ...) then captures the error as if it were data, so check the exit status:
bash
if sha=$(gh api repos/OWNER/REPO/contents/README.md --jq .sha); then echo "$sha"; else echo "fetch failed" >&2; fi
-f/ -F switch gh api to POST, so GET-only endpoints answer 404. Add -X GET to send the fields as a query string:
bash
gh api -X GET search/issues -f q='repo:cli/cli is:open worktree' --jq '.total_count'
--jq takes one expression and no jq flags (a trailing -r fails with unknown shorthand flag: 'r'); string results already print raw.
--paginate --slurp cannot combine with --jq/ --template. Pipe to jq instead, or use --paginate --jq, which filters each page.
Quote endpoints containing ? - zsh treats an unquoted ? as a glob and fails with no matches found.
Exit codes ( gh help exit-codes): 0 success, 1 failure, 2 cancelled, 4 authentication required. Some commands add their own: gh pr checks exits 8 while checks are pending.
Recent gh releases added preview commands relevant to remote analysis and discovery. They are subject to change without notice.
bash
# Read a repo without cloning (see Quick Operations above)
gh repo read-file PATH --repo OWNER/REPO
gh repo read-dir PATH --repo OWNER/REPO
# GitHub Discussions - often where design rationale lives
gh discussion list --repo OWNER/REPO --search "keyword" --answered
gh discussion view <number> --repo OWNER/REPO --comments
# Agent skills on GitHub
gh skill search <query>
gh skill preview OWNER/REPO <skill> # print SKILL.md without installing
gh skill install OWNER/REPO <skill>
gh discussion list --json returns {"discussions":[...],"next":...,"totalCount":...}, so filter with --jq '.discussions[].title'. gh discussion view also accepts a comment URL ( .../discussions/123#discussioncomment-456) to show one full reply thread, and --comments cannot be combined with --json.
gh pr revert <number> (opens a PR reverting a merged PR) is a regular command, not a preview - see pull_requests.md.
For detailed documentation on specific workflows:
Core Workflows:
GitHub Operations:
Setup & Configuration:
gh auth setup-git credential helper