`gh` CLI as backend for GitHub tooling
Source: gh-collab-manager · [atrium/ai-claude-session-dispatch] Category: Pattern — GitHub integration
gh as backend — when you’re writing a personal GitHub tool, skip Octokit. Spawn the gh CLI from your Node process. gh already handles your token, pagination, retries, rate limiting, and API version drift.
What it is
Section titled “What it is”Instead of importing @octokit/rest, building an auth flow, and writing fetch-and-paginate boilerplate, use execFile('gh', [...args]). Parse the JSON output. Done. The binary running on the server is the same CLI you use from your terminal.
Why it exists
Section titled “Why it exists”The problem: Every GitHub-integrated tool you write needs:
- A token (how — env var? OAuth? device flow?)
- Pagination (every list endpoint has it)
- Rate limit handling (secondary limits aren’t fun)
- API version tracking (REST vs. GraphQL; v3 vs. v4 deprecations)
gh already solves all of these. Reusing it costs one subprocess and zero auth code.
The fix: shell out. For personal tools this is strictly better than reimplementing what gh already does.
const { execFile } = require('child_process');const { promisify } = require('util');const execFileAsync = promisify(execFile);
async function gh(args) { const { stdout } = await execFileAsync('gh', args, { maxBuffer: 10 * 1024 * 1024 }); try { return JSON.parse(stdout); } catch { return stdout; }}
// List repos:const repos = await gh(['repo', 'list', '--json', 'name,owner,visibility', '--limit', '200']);
// Add collaborator:await gh(['api', `repos/${owner}/${repo}/collaborators/${user}`, '-X', 'PUT', '-f', 'permission=push']);Use execFile (args as array) — never exec (args as a shell string). The former is injection-safe for dynamic repo names, usernames, and branch names.
How it’s used
Section titled “How it’s used”- gh-collab-manager — every GitHub operation (list repos, manage collaborators, handle invitations, check contributors, metadata, repo reset) goes through
gh - Pattern generalizes — any personal GitHub tool that runs on a machine where
gh auth loginhas happened
Gotchas
Section titled “Gotchas”ghmust be installed and authenticated. Your service’s operator needsgh auth loginon the host running your server. If you move the service to a new host, redo auth there.execFile, notexec.execpasses through a shell and opens you up to command injection on any user-controlled string.execFilepasses args as an array directly to the binary — no shell involved.- Error handling is via exit code + stderr.
execFilerejects on non-zero exit; checkerr.stderrfor thegherror message. - Rate limits are
gh’s problem until they’re not.ghhandles primary rate limits, but secondary rate limits (abuse detection) can still bite. Don’t burst-fire hundreds of operations without a backoff. - Don’t use this for third-party consumers. If your service will be used by people who aren’t you (or your own servers), proper OAuth + Octokit is better — you don’t want to be shipping a
gh-binary dependency to every user. gh apiis the escape hatch. Any GitHub endpointghdoesn’t have a first-class command for can still be reached withgh api ...— use it for everything. Full REST + GraphQL coverage.- Output format varies by subcommand. Some subcommands default to human-readable; use
--jsonand an explicit field list to get stable machine-parseable output. - No streaming.
ghemits JSON when the whole command completes. Long-running operations buffer in memory. For paginated commands, usegh api --paginate ....
See also
Section titled “See also”- projects/gh-collab-manager — running instance
- patterns/audit-log-append-only-json — what gh-collab-manager uses to track which
ghoperations have been run