🐹 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 |
4× 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
- (Low effort, high consistency) Swap
client.Get → DoWithContext in update_check.go and normalize "GET" → http.MethodGet.
- (Medium effort, removes a runtime dependency) Replace
gh.Exec in remote_resolve_sha.go with the native REST client + api.HTTPError status branching.
- (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 · ◷
🐹 Go Fan Report: github.com/cli/go-gh/v2
Module Overview
go-ghis the GitHub CLI team's official Go module for buildingghextensions 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 agh.Exechelper to shell out to theghbinary. Its clients transparently reusegh authcredentials and honorGH_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.
pkg/api(primary),pkg/repository, rootgh(Exec)api.NewRESTClient,api.DefaultRESTClient,api.DefaultGraphQLClient,api.ClientOptions{Host, Timeout, AuthToken},(*RESTClient).DoWithContext,(*RESTClient).Get,repository.Current,gh.ExecPer-file breakdown
pkg/parser/remote_client.goapi.NewRESTClient+DoWithContext;createRESTClientForHosthelperpkg/parser/remote_download_file.goDoWithContextcontents fetchpkg/parser/remote_list_files.goDoWithContextdirectory listingspkg/parser/remote_resolve_sha.gogh.Exec("api", ...)ref→SHA, with git ls-remote + unauth fallbackspkg/cli/bootstrap_profile_actions_repo.goapi.NewRESTClient(secretSetClientOptions(""))pkg/cli/copilot_billing_check.goDefaultRESTClient+DoWithContext("GET", ...)pkg/cli/secret_set_command.goNewRESTClient(secretSetClientOptions(apiBase))pkg/cli/update_cooldown.goDefaultRESTClient+DoWithContext("GET", ...)pkg/cli/update_check.goNewRESTClient(gitHubDotComRESTClientOptions())+client.Get(...)pkg/workflow/repository_features_validation.goDefaultGraphQLClient+DoWithContext,DefaultRESTClient,repository.Current()Research Findings
v2.13.0— the latest release (2025-11-04). ✅ Up to date, no upgrade needed.gh: they reuse host auth + enterprise config and supportcontextcancellation viaDoWithContext.Recent Updates (v2.13.0)
pkg/api(bugfix).replacesprig template function.HandleHTTPErrordoes not close the response body.Best Practices
DoWithContext(context/cancellation) over the context-lessGet.*api.HTTPError(.StatusCode) rather than parsing error strings.gh.Execwhere feasible — no external-binary dependency and real cancellation.Improvement Opportunities
🏃 Quick Wins
"GET"(copilot_billing_check.go:30,update_cooldown.go:69,repository_features_validation.go:331) whilepkg/parseruseshttp.MethodGet. Unify onhttp.MethodGet.DoWithContextinupdate_check.go.getLatestReleasestill usesclient.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
ghsubprocess inremote_resolve_sha.go.resolveRefToSHAshells out viagh.Exec("api", ...)— the only place depending on theghbinary being on PATH. The neighboringcreateRESTClientForHost(host)helper already builds exactly the client needed; migrating toclient.DoWithContext(ctx, http.MethodGet, "repos/{owner}/{repo}/commits/{ref}", nil, &out)removes the external-binary dependency, adds nativecontextcancellation, and mirrors the rationale already documented atrepository_features_validation.go:276("native GraphQL client — no gh binary dependency, native context/cancel support").📐 Best Practice Alignment
api.HTTPErrorstatus, not stderr strings. The auth-fallback logic inremote_resolve_sha.goinspectsghstderr viagitutil.IsAuthError. After migrating to a native client, useerrors.As(err, &api.HTTPError{})+StatusCode == 401/403— more robust and locale-independent than string matching.🔧 General Improvements
ClientOptionsbuilders.createRESTClientForHost,secretSetClientOptions, andgitHubDotComRESTClientOptionsall seedTimeout: constants.DefaultHTTPClientTimeout. A single helper that applies the default timeout and lets callers overrideHost/AuthTokenremoves the repeated wiring and prevents a future builder from silently dropping the timeout.fetchPublicGitHubContentsAPIandresolveRefToSHAViaPublicAPIuse rawnet/httpbecause 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
client.Get→DoWithContextinupdate_check.goand normalize"GET"→http.MethodGet.gh.Execinremote_resolve_sha.gowith the native REST client +api.HTTPErrorstatus branching.ClientOptionsbuilders into one timeout-seeding helper.Next Steps
Generated by Go Fan
Module summary saved to: scratchpad/mods/cli-go-gh.md