Skip to content

[go-fan] Go Module Review: go-gh (github.com/cli/go-gh/v2)Β #43983

Description

@github-actions

🐹 Go Fan Report: cli/go-gh

Today's pick, selected round-robin by most-recent upstream activity: github.com/cli/go-gh/v2 @ v2.13.0 β€” the most recently pushed direct dependency (upstream pushed_at 2026-07-06). Fittingly, it's GitHub's own Go toolkit and one of the load-bearing deps in gh-aw. πŸŽ‰

Module Overview

go-gh is the official Go SDK behind the gh CLI. It hands you authenticated REST and GraphQL clients that auto-resolve host, token, and config from the local gh environment, plus gh.Exec (shell out to the gh binary) and repository.Current() for repo context.

Current Usage in gh-aw

  • Files: 9 non-test files across pkg/parser, pkg/cli, pkg/workflow
  • Import Count: 13 import lines (incl. tests)
  • Key APIs Used: api.NewRESTClient / api.DefaultRESTClient, RESTClient.DoWithContext, RESTClient.Get, api.ClientOptions{Timeout,Host}, gh.Exec, repository.Current

The REST client is used well β€” pkg/parser centralizes construction in createRESTClientForHost and threads context through DoWithContext, with a layered fallback chain (authenticated API β†’ git ls-remote/clone β†’ unauthenticated public API) that's genuinely thoughtful.

Per-file breakdown
File Usage
pkg/parser/remote_client.go createRESTClientForHost, DoWithContext (Contents API)
pkg/parser/remote_download_file.go REST client, symlink resolution
pkg/parser/remote_list_files.go REST client, dir/recursive listing
pkg/parser/remote_resolve_sha.go gh.Exec("api", ...) commit lookup
pkg/cli/copilot_billing_check.go DoWithContext w/ 3s timeout
pkg/cli/update_check.go NewRESTClient(opts)
pkg/cli/update_cooldown.go NewRESTClient(ClientOptions{})
pkg/cli/secret_set_command.go NewRESTClient(opts)
pkg/workflow/repository_features_validation.go DefaultRESTClient+Get, gh.Exec GraphQL, repository.Current

Research Findings

  • Version: v2.13.0 is the latest (2025-11-04); requires Go 1.25.0 β€” repo is on go 1.26.3 βœ… and already on the newest tag.
  • v2.13.0 changes: expose sprig replace template func, handle HTTP 205 correctly, bump to Go 1.25.0. No breaking changes vs v2.12.x β€” no upgrade work needed.

Best Practices (from maintainers)

  • Prefer the native api.RESTClient / api.GraphQLClient over shelling out to gh when you're already in Go β€” no dependency on the gh binary, native context support, structured api.HTTPError (with StatusCode).
  • GraphQLClient offers typed Query/Mutate (struct-derived) and raw Do/DoWithContext (query string) variants.

Improvement Opportunities

πŸƒ Quick Wins

  • Swap the discussions gh.Exec("api","graphql",...) for a native GraphQL client β€” pkg/workflow/repository_features_validation.go:281. It's the only GraphQL call and the only reason that check needs the gh binary on PATH. api.NewGraphQLClient(...).DoWithContext(ctx, checkRepositoryHasDiscussionsQuery, map[string]any{"owner":owner,"name":name}, &resp) gives you native timeout/cancel and matches the REST pattern used everywhere else.
  • Bound checkRepositoryHasIssuesUncached with a timeout β€” repository_features_validation.go:313-320 uses client.Get(...) with no context; switch to DoWithContext with a short context (mirror the 3s copilot-billing probe) so feature validation can't hang on a slow network.

✨ Feature Opportunities

  • Typed GraphQL (GraphQLClient.Query) could replace the hand-written query string + response struct for the discussions check with one schema struct.
  • Response caching (ClientOptions.EnableCache + CacheTTL) for repeated reads (update checks, repo-feature probes) to cut redundant round-trips across CLI runs.

πŸ“ Best Practice Alignment

  • remote_resolve_sha.go:93 uses gh.Exec("api", ...) for a REST commit lookup, in a package that otherwise uses api.RESTClient via createRESTClientForHost. Moving it to DoWithContext makes the resolver self-contained (no gh binary) and enables native cancellation. Nuance: the auth-error fallback currently keys on gh stderr via gitutil.IsAuthError; with the REST client it would key on api.HTTPError.StatusCode (401/403) β€” arguably cleaner. Worth weighing against the intentional simplicity of reusing gh's auth chain.
  • NewRESTClient(api.ClientOptions{}) with empty opts (copilot_billing_check.go:56, update_cooldown.go:64) is equivalent to api.DefaultRESTClient(); the latter reads more clearly as "use gh-resolved config."

πŸ”§ General Improvements

  • Consider a shared REST-client constructor in pkg/cli (mirroring parser.createRESTClientForHost) to standardize timeouts/options across the four cli call sites.

Recommendations (prioritized)

  1. Migrate the discussions GraphQL check off gh.Exec to api.GraphQLClient (removes gh-binary dependency for a validation path; highest value).
  2. Add a timeout to checkRepositoryHasIssuesUncached.
  3. Optionally migrate remote_resolve_sha.go REST lookup to DoWithContext, adapting the auth-error detection to api.HTTPError.
  4. Tidy: DefaultRESTClient() over empty-opts NewRESTClient.

Next Steps

  • No dependency bump required β€” already on latest v2.13.0.
  • The two gh.Exec sites are the main modernization targets; each is a self-contained, low-risk change with existing test coverage nearby (copilot_billing_check_test.go, remote_fetch_integration_test.go).

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

Generated by 🐹 Go Fan Β· 167.4 AIC Β· βŒ– 13.5 AIC Β· ⊞ 7.3K Β· β—·

  • expires on Jul 8, 2026, 1:05 AM UTC-08:00

Metadata

Metadata

Labels

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions