Overview
Relevant Files
README.mdcmd/gh/main.gointernal/ghcmd/cmd.godocs/project-layout.mdgo.mod
GitHub CLI (gh) is an official command-line tool that brings GitHub functionality directly to your terminal. It enables developers to manage pull requests, issues, repositories, and other GitHub concepts without leaving their shell environment.
What is GitHub CLI?
gh is a standalone tool (not a proxy to git) that provides a modern, user-friendly interface for GitHub operations. It supports GitHub.com, GitHub Enterprise Cloud, and GitHub Enterprise Server 2.20+ across macOS, Windows, and Linux.
Core Architecture
The project follows a modular Go architecture:
cmd/gh/main.go → Entry point that initializes the CLI
internal/ghcmd/cmd.go → Main command dispatcher and error handling
pkg/cmd/ → Individual command implementations
api/ → GitHub API client and GraphQL queries
pkg/cmdutil/ → Shared command utilities and factories
Execution Flow
When you run a command like gh issue list --limit 5:
- The
main()function incmd/gh/main.goinitializes the root command - The root command dispatcher routes to the appropriate subcommand
- Command-specific logic executes (e.g.,
pkg/cmd/issue/list/list.go) - Results are written to stdout/stderr via IO streams
- Exit codes indicate success or failure
Key Components
Command Structure: Commands follow the pattern pkg/cmd/<command>/<subcommand>/<subcommand>.go. Help text is embedded directly in command source code and auto-converted to manual pages.
API Layer: The api/ package handles GitHub GraphQL queries and REST API interactions. Query builders construct dynamic GraphQL requests for different data types (PRs, issues, repos, etc.).
Configuration & Auth: The internal/config/ package manages user settings and authentication tokens. The internal/authflow/ handles OAuth login flows.
Error Handling: Exit codes distinguish between different failure modes:
0= Success1= General error2= User cancellation4= Authentication error8= Pending operation
Extension System
gh supports extensions—external commands that integrate seamlessly with the CLI. Extensions are discovered and executed as subcommands, allowing community contributions without modifying core code.
Development Workflow
Loading diagram...
Testing Strategy
Tests use mocking and stubbing to avoid real API calls, filesystem operations, or git commands. Table-driven tests verify command behavior across different inputs. The pkg/httpmock/ package provides HTTP mocking for API testing.
Architecture & Command System
Relevant Files
pkg/cmd/root/root.gopkg/cmd/factory/default.gopkg/cmdutil/factory.gointernal/ghcmd/cmd.go
The GitHub CLI is built on a hierarchical command architecture powered by the Cobra framework. Understanding how commands are structured and initialized is essential for extending the CLI.
Command Hierarchy
The CLI uses a tree-based command structure with three levels:
- Root Command (
gh) - The main entry point defined inpkg/cmd/root/root.go - Top-level Commands (
gh issue,gh pr,gh repo) - Organized by domain - Subcommands (
gh issue create,gh pr list) - Specific actions within a domain
Commands are registered in NewCmdRoot() and organized into logical groups: core commands, GitHub Actions commands, and extension commands.
The Factory Pattern
The cmdutil.Factory struct (in pkg/cmdutil/factory.go) is the dependency injection container for all commands. It provides:
- IOStreams - Standard input/output/error handling
- HttpClient - Authenticated HTTP requests to GitHub API
- GitClient - Git operations and repository detection
- Config - User configuration and authentication state
- BaseRepo - Repository resolution logic
- Prompter - Interactive user prompts
- ExtensionManager - Third-party command management
The factory is initialized in pkg/cmd/factory/default.go with careful dependency ordering. Each field is lazily initialized as a function, allowing commands to request only what they need.
Repository Resolution
Two repository resolution strategies exist:
- BaseRepoFunc - Selects the first remote in priority order:
upstream>github>origin - SmartBaseRepoFunc - Uses the GitHub API to resolve repository networks and respects user-configured defaults
Commands requiring intelligent repository detection (PR, issue, repo operations) use SmartBaseRepoFunc, while simpler commands use the basic resolver.
Command Initialization Flow
Loading diagram...
Adding New Commands
To add a new command:
- Create a package at
pkg/cmd/newcommand/ - Implement
NewCmdNewCommand(f *cmdutil.Factory) *cobra.Command - Register in
NewCmdRoot()by callingcmd.AddCommand(newcommandCmd.NewCmdNewCommand(f)) - For commands needing repository context, use the
repoResolvingCmdFactoryvariant
Commands should encapsulate their logic within their package and avoid polluting global packages like api or utils.
Error Handling
The CLI defines specific exit codes in internal/ghcmd/cmd.go:
0- Success1- General error2- User cancellation4- Authentication failure8- Pending operation
Authentication checks are enforced via PersistentPreRunE in the root command, with exceptions for commands that don't require authentication (e.g., auth login, version).
GitHub API Integration
Relevant Files
api/client.goapi/http_client.goapi/query_builder.goapi/queries_pr.goapi/queries_issue.go
The GitHub API integration layer provides a unified interface for communicating with GitHub's GraphQL and REST APIs. It abstracts authentication, error handling, and query construction into reusable components.
Core Architecture
The layer consists of three main components:
- HTTP Client (
http_client.go) - Manages HTTP transport, authentication tokens, and caching - API Client (
client.go) - Wraps GraphQL and REST operations with error handling - Query Builders (
query_builder.go,queries_*.go) - Constructs type-safe GraphQL queries
Loading diagram...
HTTP Client Setup
The NewHTTPClient function initializes an HTTP client with:
- Authentication token injection via
AddAuthTokenHeader - Optional response caching with configurable TTL
- Debug logging support
- Custom User-Agent headers
client, err := NewHTTPClient(HTTPClientOptions{
AppVersion: "2.0.0",
EnableCache: true,
CacheTTL: 5 * time.Minute,
Config: configProvider,
})
GraphQL Operations
The Client type provides three GraphQL methods:
Query()- Execute a named query with struct-based type safetyMutate()- Execute mutations for write operationsGraphQL()- Execute raw GraphQL strings with variables
All methods return GraphQLError on API errors while partially populating response data.
Query Building
The query_builder.go file defines reusable GraphQL fragments for common entities:
- Pull Requests -
PullRequestGraphQL()constructs PR field selections - Issues -
IssueGraphQL()constructs issue field selections - Repositories -
RepositoryGraphQL()constructs repo field selections
These builders handle complex nested fields like reviews, commits, and status checks:
fields := []string{"title", "reviews", "statusCheckRollup"}
fragment := PullRequestGraphQL(fields)
Error Handling
The layer provides structured error types:
HTTPError- REST API errors with OAuth scope suggestionsGraphQLError- GraphQL response errors
The ScopesSuggestion() method helps users understand missing OAuth permissions.
REST API Support
For operations not available in GraphQL, the REST() method provides direct access:
err := client.REST(hostname, "POST", "repos/owner/repo/pulls", body, &result)
The RESTWithNext() variant handles pagination via Link headers.
Authentication & Configuration
Relevant Files
pkg/cmd/auth/login/login.gointernal/authflow/flow.gointernal/config/config.gopkg/cmd/auth/shared/login_flow.gointernal/keyring/keyring.gopkg/cmd/auth/shared/oauth_scopes.go
Overview
GitHub CLI authentication is built on a multi-layered system that supports OAuth flows, personal access tokens, environment variables, and secure credential storage. The system handles multiple GitHub hosts (including Enterprise instances) and supports switching between authenticated users on the same host.
Authentication Flow
Loading diagram...
Key Components
Login Command (pkg/cmd/auth/login/login.go)
- Entry point for authentication with flags for hostname, scopes, git protocol, and storage options
- Supports three authentication modes: interactive browser flow, token input, or environment variables
- Validates tokens have minimum required scopes:
repo,read:org, andgist
OAuth Flow (internal/authflow/flow.go)
- Implements GitHub's OAuth device flow using the embedded GitHub CLI OAuth app
- Handles browser opening, clipboard code copying, and success page rendering
- Retrieves authenticated user login via GraphQL query
Configuration System (internal/config/config.go)
- Manages host-specific and global settings through YAML config files
- Supports per-user token storage for multi-account scenarios
- Provides
AuthConfiginterface for token resolution with fallback chain: environment variables > keyring > plain text config
Credential Storage (internal/keyring/keyring.go)
- Wraps system keyring (macOS Keychain, Windows Credential Manager, Linux Secret Service)
- Implements 3-second timeout to prevent hangs on unresponsive keyring services
- Falls back to plain text storage if keyring is unavailable or times out
Scope Validation
Tokens are validated against minimum required scopes using the X-Oauth-Scopes header from GitHub API responses. The validation logic (pkg/cmd/auth/shared/oauth_scopes.go) checks for:
reposcope (required)read:orgORwrite:orgORadmin:org(at least one required)
Additional scopes can be requested during login for specific features like SSH key management (admin:public_key) or git credential helper setup.
Multi-Account Support
The configuration system supports multiple authenticated users per host:
- Tokens are stored per-user in the keyring or config file
- One user is marked as active for each host
gh auth switchallows switching between users without re-authenticationgh auth logoutremoves a user and automatically switches to another if available
Configuration Storage
Config files are stored in platform-specific directories:
- macOS:
~/.config/gh/ - Linux:
~/.config/gh/ - Windows:
%APPDATA%\GitHub CLI\
The default config includes settings for git protocol, editor, pager, browser, and accessibility options. Host-specific settings override global defaults.
Git Repository Integration
Relevant Files
git/client.gogit/command.gogit/url.gocontext/remote.gogit/objects.gogit/errors.go
The Git integration layer provides a high-level abstraction over Git operations, enabling the CLI to interact with repositories programmatically. The Client struct serves as the main entry point, wrapping Git command execution with context awareness, authentication, and error handling.
Core Architecture
Loading diagram...
Client Structure
The Client struct manages Git operations with the following key fields:
- GhPath: Path to the
ghexecutable for credential handling - RepoDir: Working directory for Git commands (passed via
-Cflag) - GitPath: Path to the Git executable, resolved lazily on first use
- Stderr, Stdin, Stdout: I/O streams for command execution
- commandContext: Allows dependency injection of command creation for testing
Command Execution
Commands are executed through two main methods:
- Command(): Creates a standard Git command with optional repository context
- AuthenticatedCommand(): Wraps commands with credential helper configuration, using
gh auth git-credentialfor authentication
The CredentialPattern type ensures type-safe credential helper configuration, preventing accidental credential leakage by requiring explicit patterns (e.g., github.com) rather than matching all hosts.
Remote Management
Remotes are parsed from git remote -v output and enriched with resolved URLs via Git config. The Remote struct contains:
- Name: Remote identifier (e.g.,
origin,upstream) - FetchURL and PushURL: Parsed
*url.URLobjects - Resolved: Optional custom resolution for GitHub Enterprise or URL translation
Remote names are sorted by priority: upstream > github > origin > others.
URL Parsing
The ParseURL() function normalizes Git remote URLs, supporting multiple formats:
- SSH URLs:
[email protected]:owner/repo.git→ normalized tossh://scheme - HTTPS URLs:
https://github.com/owner/repo.git - Git protocol:
git://github.com/owner/repo.git - Custom schemes:
git+ssh://,git+https://
Branch and Commit Operations
The Client provides methods for repository introspection:
- CurrentBranch(): Reads the checked-out branch via
symbolic-ref - Commits(): Retrieves commits between refs using null-byte-delimited format for safe parsing
- ReadBranchConfig(): Parses branch tracking configuration (remote, merge, push remote, merge base)
- ShowRefs(): Resolves fully-qualified refs to commit hashes
Error Handling
Git errors are wrapped in a GitError type that captures:
- ExitCode: The Git command's exit code
- Stderr: Standard error output for diagnostics
- err: The underlying error
Special cases include ErrNotOnAnyBranch for detached HEAD state and NotInstalled for missing Git executable.
Network Operations
Network-dependent operations use AuthenticatedCommand() to ensure credentials are available:
- Fetch(): Retrieves updates from remote
- Pull(): Fetches and merges with
--ff-only - Push(): Pushes branch with
--set-upstream - Clone(): Clones repository with target directory inference
All network commands support CommandModifier functions for customizing I/O streams or repository context.
Terminal I/O & Output Formatting
Relevant Files
pkg/iostreams/iostreams.gopkg/iostreams/color.gopkg/markdown/markdown.gointernal/tableprinter/table_printer.gopkg/jsoncolor/jsoncolor.go
The CLI uses a unified I/O abstraction layer to handle terminal output, color management, and formatted data display. This system adapts output based on terminal capabilities and user preferences.
IOStreams: Core I/O Abstraction
IOStreams is the central hub for all terminal I/O operations. It manages stdin, stdout, and stderr with awareness of terminal capabilities:
type IOStreams struct {
In fileReader // stdin
Out fileWriter // stdout
ErrOut fileWriter // stderr
// ... terminal detection, color, pager, progress tracking
}
Key capabilities:
- TTY Detection: Methods like
IsStdoutTTY(),IsStdinTTY(),IsStderrTTY()detect if output is connected to a terminal - Terminal Width:
TerminalWidth()returns the terminal width for responsive formatting (default 80 chars) - Pager Support:
StartPager()andStopPager()pipe output through external pagers (e.g.,less) - Progress Indicators:
StartProgressIndicatorWithLabel()displays spinners or textual progress - Alternate Screen Buffer:
StartAlternateScreenBuffer()enables full-screen terminal applications
ColorScheme: Intelligent Color Management
ColorScheme adapts colors based on terminal capabilities and user preferences:
type ColorScheme struct {
Enabled bool // Color enabled at all
EightBitColor bool // 256-color support
TrueColor bool // 24-bit RGB support
Accessible bool // Use accessible base-16 colors
ColorLabels bool // Color labels with RGB hex values
Theme string // "light", "dark", or "none"
}
Methods provide semantic coloring: Bold(), Red(), Green(), Cyan(), Muted(), TableHeader(), and Label() for custom RGB colors. Colors gracefully degrade when disabled.
TablePrinter: Formatted Data Output
TablePrinter renders aligned, colored tables with TTY-aware formatting:
tp := tableprinter.New(ios, tableprinter.WithHeader("ID", "NAME", "STATUS"))
tp.AddField(id, tableprinter.WithColor(cs.Cyan))
tp.AddField(name)
tp.AddTimeField(now, createdAt, cs.Muted) // Fuzzy time in TTY, RFC3339 otherwise
tp.EndRow()
tp.Render()
In TTY mode, headers are uppercase and underlined. In non-TTY mode, output is plain text suitable for scripting.
JSON Colorization
jsoncolor.Write() outputs syntax-highlighted JSON with ANSI escape codes:
if ios.ColorEnabled() {
jsoncolor.Write(ios.Out, jsonReader, " ") // indent for pretty-printing
}
Colors: delimiters (bright white), keys (bright blue), strings (green), booleans (yellow), null (cyan).
Markdown Rendering
markdown.Render() converts Markdown to terminal-friendly output using Glamour:
rendered, err := markdown.Render(text,
markdown.WithTheme(ios.TerminalTheme()),
markdown.WithWrap(ios.TerminalWidth()),
)
Respects GLAMOUR_STYLE environment variable and terminal theme detection.
Platform-Specific Handling
On Windows without virtual terminal support, colorable translates ANSI escape sequences to console syscalls. The system preserves original file descriptors while wrapping writers for compatibility.
Usage Pattern
Commands typically follow this pattern:
ios := iostreams.System() // Detect terminal capabilities
cs := ios.ColorScheme() // Get color scheme
if ios.IsStdoutTTY() {
// Interactive output: colors, tables, pagers
} else {
// Scripting output: plain text, JSON
}
Extension System & Plugins
Relevant Files
pkg/cmd/extension/manager.gopkg/cmd/extension/extension.gopkg/extensions/extension.gopkg/cmd/extension/command.go
The extension system allows users to create and install custom commands that extend GitHub CLI functionality. Extensions are standalone repositories prefixed with gh- that provide additional executable commands.
Architecture Overview
Loading diagram...
Extension Types
Extensions come in three varieties:
-
Binary Extensions - Precompiled binaries distributed via GitHub releases. The manager detects platform-specific assets (e.g.,
linux-amd64,darwin-arm64) and downloads the appropriate binary. Amanifest.ymlfile tracks metadata including owner, repository, tag, and pin status. -
Git Extensions - Script-based extensions cloned directly from a repository. These contain executable scripts (Bash, Python, Ruby, etc.) in the repository root. Version tracking uses git commit SHAs and remote HEAD references.
-
Local Extensions - Development extensions installed from a local directory via symlink (or file path on Windows). Useful during development; cannot be upgraded automatically.
Manager Operations
The Manager type orchestrates all extension lifecycle operations:
- Install - Clones or downloads an extension, optionally pinning to a specific version (tag for binaries, commit SHA for git extensions)
- List - Enumerates installed extensions by scanning the extensions directory for
gh-*prefixed entries - Upgrade - Updates extensions to latest versions, with special handling for pinned and local extensions
- Remove - Deletes extension directories and cleans up update metadata
- Dispatch - Executes an extension by name, forwarding all arguments to the executable
- Create - Scaffolds new extensions with templates (script, Go binary, or other precompiled)
Extension Interface
The Extension interface provides a unified API for querying extension metadata:
Name()- Extension name withoutgh-prefixPath()- Filesystem path to the executableURL()- Repository URL (parsed from manifest or git remote)CurrentVersion()- Installed version (tag for binary, commit SHA for git)LatestVersion()- Latest available version (fetched from releases or remote HEAD)IsPinned()- Whether extension is locked to a specific versionUpdateAvailable()- Boolean indicating if an upgrade is possibleIsBinary()/IsLocal()- Type predicates
Version Management
Binary extensions store metadata in manifest.yml (owner, name, host, tag, pin status). Git extensions track versions via git commit SHAs and use .pin-{SHA} marker files to indicate pinned versions. The manager checks for updates once every 24 hours by default and displays upgrade notices to users.
Platform Support
The system supports 40+ platform combinations (Linux, macOS, Windows, BSD, etc.) across multiple architectures. On ARM-based Macs, the manager falls back to amd64 binaries with Rosetta 2 emulation if native ARM binaries are unavailable. Windows extensions are dispatched through sh.exe to support shebang lines.
Core Command Implementations
Relevant Files
pkg/cmd/pr/pr.gopkg/cmd/issue/issue.gopkg/cmd/repo/repo.gopkg/cmd/api/api.gopkg/cmdutil/factory.gopkg/cmd/factory/default.go
The CLI is built on a hierarchical command structure using Cobra, with a Factory pattern providing dependency injection. Each major command (PR, Issue, Repo, API) is a parent command that groups related subcommands.
Command Hierarchy
Commands follow a two-level structure: parent commands with grouped subcommands. For example, gh pr is the parent with subcommands like create, list, view, and merge. The cmdutil.AddGroup() helper organizes subcommands into logical groups displayed in help text.
cmdutil.AddGroup(cmd, "General commands",
cmdList.NewCmdList(f, nil),
cmdCreate.NewCmdCreate(f, nil),
)
cmdutil.AddGroup(cmd, "Targeted commands",
cmdView.NewCmdView(f, nil),
cmdMerge.NewCmdMerge(f, nil),
)
The Factory Pattern
The cmdutil.Factory is the central dependency injection container. It provides:
- IO Streams – stdin, stdout, stderr with TTY detection
- HTTP Client – authenticated API requests with caching
- Git Client – local repository operations
- Config – user authentication and settings
- BaseRepo – current repository resolution
- Remotes – git remote discovery and filtering
The factory is initialized once in pkg/cmd/factory/default.go with lazy-loaded dependencies. Commands receive the factory and call its methods to access services.
Repository Resolution
Two strategies exist for determining the base repository:
- BaseRepoFunc – Returns the first remote in priority order (upstream, github, origin)
- SmartBaseRepoFunc – Uses the GitHub API to resolve repository networks and respects user-configured defaults via
gh repo set-default
Commands requiring intelligent repo resolution use SmartBaseRepoFunc, while simpler commands use the basic resolver.
Core Commands
PR Command manages pull requests with subcommands for create, list, view, checkout, merge, review, and more. Supports both number and URL arguments.
Issue Command handles GitHub issues with similar structure: create, list, view, comment, close, edit, and lifecycle operations.
Repo Command operates on repositories: clone, fork, create, view, sync, and configuration. Includes specialized subcommands for deploy keys, licenses, and gitignore templates.
API Command makes authenticated GraphQL and REST requests. Supports field interpolation, pagination, response filtering, and custom headers. Enables direct API access without CLI abstractions.
Command Options Pattern
Each command defines an Options struct capturing user input and dependencies. The NewCmd* function creates the Cobra command and binds flags to the options struct. A run function executes the command logic using the populated options.
type Options struct {
IO *iostreams.IOStreams
Config func() (gh.Config, error)
// ... command-specific fields
}
func NewCmdExample(f *cmdutil.Factory) *cobra.Command {
opts := &Options{}
cmd := &cobra.Command{
RunE: func(cmd *cobra.Command, args []string) error {
return exampleRun(opts)
},
}
// Bind flags to opts fields
return cmd
}
Error Handling
Commands use cmdutil.FlagErrorf() for flag validation errors and return standard Go errors for runtime failures. The factory's IOStreams provides color-aware output for error messages in TTY environments.