Skip to content

[go-fan] Go Module Review: cli/go-gh #47535

Description

@github-actions

🐹 Go Fan Report: github.com/cli/go-gh/v2

Module Overview

go-gh is the GitHub CLI team's official Go module for building gh extensions and programs that call the GitHub REST/GraphQL APIs. It ships native, auth-aware REST and GraphQL clients (pkg/api), current-repo resolution (pkg/repository), and a gh.Exec helper to shell out to the gh binary. Its clients transparently reuse gh auth credentials and honor GH_HOST/enterprise host config.

Current Usage in gh-aw

gh-aw leans on go-gh as its primary GitHub API layer for remote workflow resolution, update checks, secret setting, and repository-feature validation.

  • Files: 10 non-test files (+3 test files)
  • Packages used: pkg/api (primary), pkg/repository, root gh (Exec)
  • Key APIs Used: api.NewRESTClient, api.DefaultRESTClient, api.DefaultGraphQLClient, api.ClientOptions{Host, Timeout, AuthToken}, (*RESTClient).DoWithContext, (*RESTClient).Get, repository.Current, gh.Exec
Per-file breakdown
File Usage
pkg/parser/remote_client.go api.NewRESTClient + DoWithContext; createRESTClientForHost helper
pkg/parser/remote_download_file.go DoWithContext contents fetch
pkg/parser/remote_list_files.go DoWithContext directory listings
pkg/parser/remote_resolve_sha.go gh.Exec("api", ...) ref→SHA, with git ls-remote + unauth fallbacks
pkg/cli/bootstrap_profile_actions_repo.go api.NewRESTClient(secretSetClientOptions(""))
pkg/cli/copilot_billing_check.go DefaultRESTClient + DoWithContext("GET", ...)
pkg/cli/secret_set_command.go NewRESTClient(secretSetClientOptions(apiBase))
pkg/cli/update_cooldown.go DefaultRESTClient + DoWithContext("GET", ...)
pkg/cli/update_check.go NewRESTClient(gitHubDotComRESTClientOptions()) + client.Get(...)
pkg/workflow/repository_features_validation.go DefaultGraphQLClient + DoWithContext, DefaultRESTClient, repository.Current()

Research Findings

  • Repository: https://github.com/cli/go-gh
  • Pinned version: v2.13.0 — the latest release (2025-11-04). ✅ Up to date, no upgrade needed.
  • The native clients are the recommended path over shelling out to gh: they reuse host auth + enterprise config and support context cancellation via DoWithContext.

Recent Updates (v2.13.0)

  • Proper handling of HTTP status 205 in pkg/api (bugfix).
  • Exposed the replace sprig template function.
  • Docs clarified that HandleHTTPError does not close the response body.
  • Toolchain bumped to Go 1.25.

Best Practices

  • Prefer DoWithContext (context/cancellation) over the context-less Get.
  • Branch on the returned *api.HTTPError (.StatusCode) rather than parsing error strings.
  • Reuse the native REST/GraphQL clients instead of gh.Exec where feasible — no external-binary dependency and real cancellation.

Improvement Opportunities

🏃 Quick Wins

  • Standardize the HTTP verb. Three call sites pass the literal "GET" (copilot_billing_check.go:30, update_cooldown.go:69, repository_features_validation.go:331) while pkg/parser uses http.MethodGet. Unify on http.MethodGet.
  • Use DoWithContext in update_check.go. getLatestRelease still uses client.Get(...) (lines 197, 209) — the only remaining context-less REST calls in the module. Switching gives cancellation and matches every other call site.

✨ Feature Opportunities

  • Drop the gh subprocess in remote_resolve_sha.go. resolveRefToSHA shells out via gh.Exec("api", ...) — the only place depending on the gh binary being on PATH. The neighboring createRESTClientForHost(host) helper already builds exactly the client needed; migrating to client.DoWithContext(ctx, http.MethodGet, "repos/{owner}/{repo}/commits/{ref}", nil, &out) removes the external-binary dependency, adds native context cancellation, and mirrors the rationale already documented at repository_features_validation.go:276 ("native GraphQL client — no gh binary dependency, native context/cancel support").

📐 Best Practice Alignment

  • Branch on api.HTTPError status, not stderr strings. The auth-fallback logic in remote_resolve_sha.go inspects gh stderr via gitutil.IsAuthError. After migrating to a native client, use errors.As(err, &api.HTTPError{}) + StatusCode == 401/403 — more robust and locale-independent than string matching.

🔧 General Improvements

  • Consolidate ClientOptions builders. createRESTClientForHost, secretSetClientOptions, and gitHubDotComRESTClientOptions all seed Timeout: constants.DefaultHTTPClientTimeout. A single helper that applies the default timeout and lets callers override Host/AuthToken removes the repeated wiring and prevents a future builder from silently dropping the timeout.
  • Leave the unauthenticated fallbacks as-is. fetchPublicGitHubContentsAPI and resolveRefToSHAViaPublicAPI use raw net/http because go-gh's REST client expects an auth token. This is intentional (go-gh has no first-class unauthenticated mode) — documented so it isn't "fixed" later.

Recommendations

  1. (Low effort, high consistency) Swap client.GetDoWithContext in update_check.go and normalize "GET"http.MethodGet.
  2. (Medium effort, removes a runtime dependency) Replace gh.Exec in remote_resolve_sha.go with the native REST client + api.HTTPError status branching.
  3. (Low effort, DRY) Fold the three ClientOptions builders into one timeout-seeding helper.

Next Steps


Generated by Go Fan
Module summary saved to: scratchpad/mods/cli-go-gh.md

Generated by 🐹 Go Fan · age00 155.3 AIC · ⌖ 13.6 AIC · ⊞ 7.2K ·

  • expires on Jul 24, 2026, 12:53 AM UTC-08:00

Metadata

Metadata

Labels

automationcookieIssue Monster Loves Cookies!dependenciesPull requests that update a dependency filego-fan

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions