From 9a0119c79f13b7b28534f19e7bc44c9a09448244 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Jul 2026 21:36:27 +0000 Subject: [PATCH 1/9] Initial plan From bf2a55e152445aa14ca0fcb2c30e594850c13ce6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Jul 2026 21:55:55 +0000 Subject: [PATCH 2/9] Add parser host env coverage and tighten assertions Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/parser/github_host_test.go | 77 ++++++++++++++++++++++++++++++++-- 1 file changed, 73 insertions(+), 4 deletions(-) diff --git a/pkg/parser/github_host_test.go b/pkg/parser/github_host_test.go index b2601e8afd9..b6bc5cc5de2 100644 --- a/pkg/parser/github_host_test.go +++ b/pkg/parser/github_host_test.go @@ -1,13 +1,75 @@ -//go:build !integration - package parser import ( "testing" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) +func TestGetGitHubHost(t *testing.T) { + tests := []struct { + name string + serverURL string + enterpriseHost string + githubHost string + ghHost string + expectedHost string + }{ + { + name: "GITHUB_SERVER_URL wins over others", + serverURL: "acme.ghe.com/redacted", + enterpriseHost: "enterprise.ghe.com", + githubHost: "github-host.ghe.com", + ghHost: "gh-host.ghe.com", + expectedHost: "https://acme.ghe.com/redacted", + }, + { + name: "host without scheme gets https prefix", + serverURL: "", + enterpriseHost: "acme.ghe.com", + githubHost: "", + ghHost: "", + expectedHost: "https://acme.ghe.com", + }, + { + name: "trailing slash stripped", + serverURL: "", + enterpriseHost: "", + githubHost: "acme.ghe.com/", + ghHost: "", + expectedHost: "https://acme.ghe.com", + }, + { + name: "GH_HOST used when others are empty", + serverURL: "", + enterpriseHost: "", + githubHost: "", + ghHost: "acme.ghe.com", + expectedHost: "https://acme.ghe.com", + }, + { + name: "all vars empty falls back to github.com", + serverURL: "", + enterpriseHost: "", + githubHost: "", + ghHost: "", + expectedHost: "https://github.com", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Setenv("GITHUB_SERVER_URL", tt.serverURL) + t.Setenv("GITHUB_ENTERPRISE_HOST", tt.enterpriseHost) + t.Setenv("GITHUB_HOST", tt.githubHost) + t.Setenv("GH_HOST", tt.ghHost) + + host := GetGitHubHost() + require.Equal(t, tt.expectedHost, host) + }) + } +} + func TestGetGitHubHostForRepo_PublicOrgFallback(t *testing.T) { tests := []struct { name string @@ -23,6 +85,13 @@ func TestGetGitHubHostForRepo_PublicOrgFallback(t *testing.T) { gheHost: "myorg.ghe.com", expectedHost: "https://myorg.ghe.com", }, + { + name: "empty gheHost falls back to public for non-fallback owner", + owner: "acme", + repo: "repo", + gheHost: "", + expectedHost: "https://github.com", + }, { name: "github owner uses public host", owner: "github", @@ -54,7 +123,7 @@ func TestGetGitHubHostForRepo_PublicOrgFallback(t *testing.T) { t.Setenv("GH_HOST", "") host := GetGitHubHostForRepo(tt.owner, tt.repo) - assert.Equal(t, tt.expectedHost, host, "GetGitHubHostForRepo(%q, %q)", tt.owner, tt.repo) + require.Equal(t, tt.expectedHost, host, "GetGitHubHostForRepo(%q, %q)", tt.owner, tt.repo) }) } } From 6472d4686ee142de992c71e9f76013f56549f35e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 21 Jul 2026 04:14:23 +0000 Subject: [PATCH 3/9] placeholder Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com> --- .../workflows/smoke-call-workflow.lock.yml | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/.github/workflows/smoke-call-workflow.lock.yml b/.github/workflows/smoke-call-workflow.lock.yml index 15a3ed21b8c..eacad788ec7 100644 --- a/.github/workflows/smoke-call-workflow.lock.yml +++ b/.github/workflows/smoke-call-workflow.lock.yml @@ -619,6 +619,11 @@ jobs: "inputSchema": { "additionalProperties": false, "properties": { + "aw_context": { + "default": "", + "description": "Agent caller context (used internally by Agentic Workflows).", + "type": "string" + }, "payload": { "description": "Input parameter 'payload' for workflow smoke-workflow-call", "type": "string" @@ -1104,15 +1109,25 @@ jobs: needs: safe_outputs if: needs.safe_outputs.outputs.call_workflow_name == 'smoke-workflow-call' # Imported from called workflow "smoke-workflow-call" because GitHub requires the caller job to grant permissions requested by reusable workflow jobs. - # Review the called workflow's frontmatter permissions in ./.github/workflows/smoke-workflow-call.md. + # Review the called workflow's job-level permissions in ./.github/workflows/smoke-workflow-call.lock.yml. permissions: + actions: read contents: read - pull-requests: read + issues: write + pull-requests: write uses: ./.github/workflows/smoke-workflow-call.lock.yml with: + aw_context: ${{ fromJSON(needs.safe_outputs.outputs.call_workflow_payload).aw_context }} payload: ${{ needs.safe_outputs.outputs.call_workflow_payload }} task-description: ${{ fromJSON(needs.safe_outputs.outputs.call_workflow_payload)['task-description'] }} - secrets: inherit + secrets: + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} + GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} + GH_AW_OTEL_GRAFANA_AUTHORIZATION: ${{ secrets.GH_AW_OTEL_GRAFANA_AUTHORIZATION }} + GH_AW_OTEL_GRAFANA_ENDPOINT: ${{ secrets.GH_AW_OTEL_GRAFANA_ENDPOINT }} + GH_AW_OTEL_SENTRY_AUTHORIZATION: ${{ secrets.GH_AW_OTEL_SENTRY_AUTHORIZATION }} + GH_AW_OTEL_SENTRY_ENDPOINT: ${{ secrets.GH_AW_OTEL_SENTRY_ENDPOINT }} conclusion: needs: From 8e2c5d79e9fefc17d3fe3839cc52605d1612f355 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 21 Jul 2026 04:17:42 +0000 Subject: [PATCH 4/9] Fix test precedence coverage and add missing build tag - Add //go:build !integration to match package convention - Replace normalisation-only cases with real precedence tests: "GITHUB_ENTERPRISE_HOST wins over GITHUB_HOST and GH_HOST" populates all three lower-priority vars so a wrong ordering would be caught - "GITHUB_HOST wins over GH_HOST" likewise sets GH_HOST to a different value to verify the ordering holds Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com> --- pkg/parser/github_host_test.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/pkg/parser/github_host_test.go b/pkg/parser/github_host_test.go index b6bc5cc5de2..d1e83c48985 100644 --- a/pkg/parser/github_host_test.go +++ b/pkg/parser/github_host_test.go @@ -1,3 +1,5 @@ +//go:build !integration + package parser import ( @@ -24,19 +26,19 @@ func TestGetGitHubHost(t *testing.T) { expectedHost: "https://acme.ghe.com/redacted", }, { - name: "host without scheme gets https prefix", + name: "GITHUB_ENTERPRISE_HOST wins over GITHUB_HOST and GH_HOST", serverURL: "", enterpriseHost: "acme.ghe.com", - githubHost: "", - ghHost: "", + githubHost: "github-host.ghe.com", + ghHost: "gh-host.ghe.com", expectedHost: "https://acme.ghe.com", }, { - name: "trailing slash stripped", + name: "GITHUB_HOST wins over GH_HOST", serverURL: "", enterpriseHost: "", githubHost: "acme.ghe.com/", - ghHost: "", + ghHost: "gh-host.ghe.com", expectedHost: "https://acme.ghe.com", }, { From 9a2afe471c5167fc6b4ed13d752e2537b12c26ee Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 21 Jul 2026 04:49:50 +0000 Subject: [PATCH 5/9] Refactor host env var resolution to unified helper - Add IsAnyGitHubHostEnvVarSet() to pkg/parser/github.go as the canonical helper for checking if any host env var is explicitly set - Add isAnyGitHubHostEnvVarSet() shim to pkg/cli/github.go delegating to the parser function; remove the duplicate local version from engine_secrets.go - Replace os.Getenv("GH_HOST") == "" bypass checks in compile_orchestrator.go, add_interactive_orchestrator.go, and setup_repository.go with isAnyGitHubHostEnvVarSet() so all four host env vars are respected - Simplify importAuthGHHost() to use getGitHubHost() + isAnyGitHubHostEnvVarSet() instead of reading only GH_HOST; update GHE auth tests to set/clear all four env vars explicitly and add coverage for GITHUB_ENTERPRISE_HOST and GITHUB_SERVER_URL - Extend detectGHESDeployment() to check all four env vars (GITHUB_SERVER_URL > GITHUB_ENTERPRISE_HOST > GITHUB_HOST > GH_HOST) instead of only GITHUB_SERVER_URL and GH_HOST Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/cli/add_interactive_orchestrator.go | 2 +- pkg/cli/compile_orchestrator.go | 2 +- pkg/cli/engine_secrets.go | 13 -------- pkg/cli/github.go | 7 ++++ pkg/cli/import_url_fetcher.go | 24 ++++---------- pkg/cli/import_url_fetcher_test.go | 43 +++++++++++++++++++++++++ pkg/cli/init.go | 31 +++++++----------- pkg/cli/setup_repository.go | 2 +- pkg/parser/github.go | 13 ++++++++ 9 files changed, 85 insertions(+), 52 deletions(-) diff --git a/pkg/cli/add_interactive_orchestrator.go b/pkg/cli/add_interactive_orchestrator.go index 866f6dc28cb..e89330a2528 100644 --- a/pkg/cli/add_interactive_orchestrator.go +++ b/pkg/cli/add_interactive_orchestrator.go @@ -83,7 +83,7 @@ func RunAddInteractive(ctx context.Context, config *AddInteractiveConfig) error config.Ctx = ctx // Auto-detect GHES host from git remote if not already set - if os.Getenv("GH_HOST") == "" { //nolint:osgetenvlibrary + if !isAnyGitHubHostEnvVarSet() { detectedHost := getHostFromOriginRemote() if detectedHost != "github.com" { addInteractiveLog.Printf("Auto-detected GHES host from git remote: %s", detectedHost) diff --git a/pkg/cli/compile_orchestrator.go b/pkg/cli/compile_orchestrator.go index 411afcae16e..7fac9bab8ee 100644 --- a/pkg/cli/compile_orchestrator.go +++ b/pkg/cli/compile_orchestrator.go @@ -27,7 +27,7 @@ func CompileWorkflows(ctx context.Context, config CompileConfig) ([]*workflow.Wo default: } - if os.Getenv("GH_HOST") == "" { //nolint:osgetenvlibrary + if !isAnyGitHubHostEnvVarSet() { if detectedHost := getHostFromOriginRemote(); detectedHost != "github.com" && detectedHost != "" { compileOrchestratorLog.Printf("Auto-detected GHES host from git remote: %s", detectedHost) workflow.SetDefaultGHHost(detectedHost) diff --git a/pkg/cli/engine_secrets.go b/pkg/cli/engine_secrets.go index 95d540673db..660c1683267 100644 --- a/pkg/cli/engine_secrets.go +++ b/pkg/cli/engine_secrets.go @@ -408,19 +408,6 @@ func buildGenericPATCreationURL() string { return buildPATCreationURL(nil) } -// isAnyGitHubHostEnvVarSet returns true when any of the environment variables -// consumed by getGitHubHost() is explicitly set. When at least one is present -// the caller has made an explicit host choice and the git-remote fallback should -// not be consulted. -func isAnyGitHubHostEnvVarSet() bool { - for _, envVar := range []string{"GITHUB_SERVER_URL", "GITHUB_ENTERPRISE_HOST", "GITHUB_HOST", "GH_HOST"} { - if os.Getenv(envVar) != "" { //nolint:osgetenvlibrary - return true - } - } - return false -} - func buildPATCreationURL(values url.Values) string { hostURL := getGitHubHost() // Only consult the git remote when the caller has not made an explicit host diff --git a/pkg/cli/github.go b/pkg/cli/github.go index 3f9b3a6a90c..5fa96c7506f 100644 --- a/pkg/cli/github.go +++ b/pkg/cli/github.go @@ -33,3 +33,10 @@ func getGitHubHostForRepo(repo string) string { // For all other repositories, use the configured GitHub host return getGitHubHost() } + +// isAnyGitHubHostEnvVarSet returns true when at least one of the environment +// variables consulted by getGitHubHost is explicitly set to a non-empty value. +// Delegates to parser.IsAnyGitHubHostEnvVarSet() for the shared implementation. +func isAnyGitHubHostEnvVarSet() bool { + return parser.IsAnyGitHubHostEnvVarSet() +} diff --git a/pkg/cli/import_url_fetcher.go b/pkg/cli/import_url_fetcher.go index 6a09e6744a0..ccd304919bf 100644 --- a/pkg/cli/import_url_fetcher.go +++ b/pkg/cli/import_url_fetcher.go @@ -8,7 +8,6 @@ import ( "mime" "net/http" "net/url" - "os" "strings" "time" @@ -305,26 +304,17 @@ func logResponseBodyVerbose(resp *http.Response) { } func importAuthGHHost() string { - ghHost := os.Getenv("GH_HOST") //nolint:osgetenvlibrary - if ghHost == "" { + // Use unified resolution (GITHUB_SERVER_URL > GITHUB_ENTERPRISE_HOST > GITHUB_HOST > GH_HOST). + // Return "" when no host env var is set so that callers do not add a + // redundant entry for github.com, which is already in defaultImportAuthHosts. + if !isAnyGitHubHostEnvVarSet() { return "" } - // GH_HOST may carry a scheme prefix; extract just the hostname. - if u, parseErr := url.Parse(ghHost); parseErr == nil && u.Host != "" { + // getGitHubHost() returns a normalized https://… URL; parse just the hostname. + if u, parseErr := url.Parse(getGitHubHost()); parseErr == nil && u.Host != "" { return strings.ToLower(u.Hostname()) } - // No scheme present — treat the whole value as a bare hostname (possibly - // with port). Strip any accidental scheme prefix or trailing path. - bare := strings.TrimPrefix(ghHost, "https://") - bare = strings.TrimPrefix(bare, "http://") - if idx := strings.IndexByte(bare, '/'); idx != -1 { - bare = bare[:idx] - } - parsed, err := url.Parse("https://" + bare) - if err == nil && parsed.Host != "" { - return strings.ToLower(parsed.Hostname()) - } - return strings.ToLower(bare) + return "" } // sanitizeHTTPError strips the request URL from a *url.Error (the error type diff --git a/pkg/cli/import_url_fetcher_test.go b/pkg/cli/import_url_fetcher_test.go index cffd67ddd1a..ff59a5165e9 100644 --- a/pkg/cli/import_url_fetcher_test.go +++ b/pkg/cli/import_url_fetcher_test.go @@ -336,6 +336,9 @@ func TestAttachImportAuthHeader_DocsGitHub_NoToken(t *testing.T) { func TestAttachImportAuthHeader_GHE_BareHostname(t *testing.T) { t.Setenv("GITHUB_TOKEN", "ghe-token") t.Setenv("GH_TOKEN", "") + t.Setenv("GITHUB_SERVER_URL", "") + t.Setenv("GITHUB_ENTERPRISE_HOST", "") + t.Setenv("GITHUB_HOST", "") t.Setenv("GH_HOST", "ghe.example.com") req, _ := http.NewRequest(http.MethodGet, "https://ghe.example.com/owner/repo/raw/main/wf.md", nil) @@ -347,6 +350,9 @@ func TestAttachImportAuthHeader_GHE_BareHostname(t *testing.T) { func TestAttachImportAuthHeader_GHE_HTTPSScheme(t *testing.T) { t.Setenv("GITHUB_TOKEN", "ghe-token") t.Setenv("GH_TOKEN", "") + t.Setenv("GITHUB_SERVER_URL", "") + t.Setenv("GITHUB_ENTERPRISE_HOST", "") + t.Setenv("GITHUB_HOST", "") t.Setenv("GH_HOST", "https://ghe.example.com") req, _ := http.NewRequest(http.MethodGet, "https://ghe.example.com/owner/repo/raw/main/wf.md", nil) @@ -358,6 +364,9 @@ func TestAttachImportAuthHeader_GHE_HTTPSScheme(t *testing.T) { func TestAttachImportAuthHeader_GHE_HTTPSchemePrefix(t *testing.T) { t.Setenv("GITHUB_TOKEN", "ghe-token") t.Setenv("GH_TOKEN", "") + t.Setenv("GITHUB_SERVER_URL", "") + t.Setenv("GITHUB_ENTERPRISE_HOST", "") + t.Setenv("GITHUB_HOST", "") t.Setenv("GH_HOST", "http://ghe.example.com") // HTTPS request → token sent. @@ -375,6 +384,9 @@ func TestAttachImportAuthHeader_GHE_HTTPSchemePrefix(t *testing.T) { func TestAttachImportAuthHeader_GHE_DifferentHost(t *testing.T) { t.Setenv("GITHUB_TOKEN", "ghe-token") t.Setenv("GH_TOKEN", "") + t.Setenv("GITHUB_SERVER_URL", "") + t.Setenv("GITHUB_ENTERPRISE_HOST", "") + t.Setenv("GITHUB_HOST", "") t.Setenv("GH_HOST", "ghe.example.com") req, _ := http.NewRequest(http.MethodGet, "https://other.example.com/workflow.md", nil) @@ -386,6 +398,9 @@ func TestAttachImportAuthHeader_GHE_DifferentHost(t *testing.T) { func TestAttachImportAuthHeader_GitHubAlongsideGHE(t *testing.T) { t.Setenv("GITHUB_TOKEN", "dual-token") t.Setenv("GH_TOKEN", "") + t.Setenv("GITHUB_SERVER_URL", "") + t.Setenv("GITHUB_ENTERPRISE_HOST", "") + t.Setenv("GITHUB_HOST", "") t.Setenv("GH_HOST", "ghe.example.com") req, _ := http.NewRequest(http.MethodGet, "https://github.com/owner/repo/raw/main/wf.md", nil) @@ -393,6 +408,34 @@ func TestAttachImportAuthHeader_GitHubAlongsideGHE(t *testing.T) { assert.Equal(t, "Bearer dual-token", req.Header.Get("Authorization"), "github.com must still be allowed when GH_HOST is also set") } +// GITHUB_ENTERPRISE_HOST resolves the auth host (unified resolution). +func TestAttachImportAuthHeader_GHE_EnterpriseHostEnvVar(t *testing.T) { + t.Setenv("GITHUB_TOKEN", "ent-token") + t.Setenv("GH_TOKEN", "") + t.Setenv("GITHUB_SERVER_URL", "") + t.Setenv("GITHUB_ENTERPRISE_HOST", "ent.example.com") + t.Setenv("GITHUB_HOST", "other.example.com") + t.Setenv("GH_HOST", "gh.example.com") + + req, _ := http.NewRequest(http.MethodGet, "https://ent.example.com/owner/repo/raw/main/wf.md", nil) + attachImportAuthHeader(req, "https://ent.example.com/owner/repo/raw/main/wf.md") + assert.Equal(t, "Bearer ent-token", req.Header.Get("Authorization"), "GITHUB_ENTERPRISE_HOST must resolve as the auth host") +} + +// GITHUB_SERVER_URL resolves the auth host (highest priority). +func TestAttachImportAuthHeader_GHE_ServerURLEnvVar(t *testing.T) { + t.Setenv("GITHUB_TOKEN", "srv-token") + t.Setenv("GH_TOKEN", "") + t.Setenv("GITHUB_SERVER_URL", "https://srv.example.com") + t.Setenv("GITHUB_ENTERPRISE_HOST", "ent.example.com") + t.Setenv("GITHUB_HOST", "") + t.Setenv("GH_HOST", "") + + req, _ := http.NewRequest(http.MethodGet, "https://srv.example.com/owner/repo/raw/main/wf.md", nil) + attachImportAuthHeader(req, "https://srv.example.com/owner/repo/raw/main/wf.md") + assert.Equal(t, "Bearer srv-token", req.Header.Get("Authorization"), "GITHUB_SERVER_URL must resolve as the auth host") +} + // TestBuildRequestLogString_RedactsAuthorization verifies that the request formatter // never exposes the raw token and shows the correct redacted form. func TestBuildRequestLogString_RedactsAuthorization(t *testing.T) { diff --git a/pkg/cli/init.go b/pkg/cli/init.go index 08dab407caf..edd6ae4ec25 100644 --- a/pkg/cli/init.go +++ b/pkg/cli/init.go @@ -337,32 +337,25 @@ func isGHESHost(host string) bool { // detectGHESDeployment returns the GHES host if the current repository's git // remote points to a GitHub Enterprise Server instance, or "" if it does not. // Detection uses the following sources in priority order: -// 1. GITHUB_SERVER_URL environment variable (set automatically inside GitHub Actions) -// 2. GH_HOST environment variable (set by the gh CLI) -// 3. The hostname extracted from the git origin remote URL +// 1. GITHUB_SERVER_URL, GITHUB_ENTERPRISE_HOST, GITHUB_HOST, GH_HOST environment variables +// 2. The hostname extracted from the git origin remote URL func detectGHESDeployment() string { - // Check GITHUB_SERVER_URL first (set inside GitHub Actions runners) - if serverURL := os.Getenv("GITHUB_SERVER_URL"); serverURL != "" { //nolint:osgetenvlibrary - // serverURL is like "https://ghes.example.com", extract just the host. - host := serverURL - for _, scheme := range []string{"https://", "http://"} { - host = strings.TrimPrefix(host, scheme) + // Check env vars in unified priority order (mirrors GetGitHubHost): + // GITHUB_SERVER_URL > GITHUB_ENTERPRISE_HOST > GITHUB_HOST > GH_HOST + for _, envVar := range []string{"GITHUB_SERVER_URL", "GITHUB_ENTERPRISE_HOST", "GITHUB_HOST", "GH_HOST"} { + rawValue := os.Getenv(envVar) //nolint:osgetenvlibrary + if rawValue == "" { + continue } - host = strings.TrimSuffix(host, "/") + host := strings.TrimPrefix(rawValue, "https://") + host = strings.TrimPrefix(host, "http://") + host = strings.TrimRight(host, "/") if isGHESHost(host) { - initLog.Printf("Detected GHES deployment from GITHUB_SERVER_URL: %s", host) + initLog.Printf("Detected GHES deployment from %s: %s", envVar, host) return host } } - // Check GH_HOST (set when using the gh CLI against an enterprise instance) - if ghHost := os.Getenv("GH_HOST"); ghHost != "" { //nolint:osgetenvlibrary - if isGHESHost(ghHost) { - initLog.Printf("Detected GHES deployment from GH_HOST: %s", ghHost) - return ghHost - } - } - // Fall back to detecting the host from the git origin remote host := getHostFromOriginRemote() if isGHESHost(host) { diff --git a/pkg/cli/setup_repository.go b/pkg/cli/setup_repository.go index bdda33189c4..7fc7431f2c5 100644 --- a/pkg/cli/setup_repository.go +++ b/pkg/cli/setup_repository.go @@ -18,7 +18,7 @@ import ( var setupRepositoryLog = logger.New("cli:setup_repository") func configureDefaultGHHostFromOriginRemoteIfUnset() { - if os.Getenv("GH_HOST") != "" { //nolint:osgetenvlibrary + if isAnyGitHubHostEnvVarSet() { return } diff --git a/pkg/parser/github.go b/pkg/parser/github.go index a721db65f47..e3448866670 100644 --- a/pkg/parser/github.go +++ b/pkg/parser/github.go @@ -40,6 +40,19 @@ func GetGitHubHost() string { return defaultHost } +// IsAnyGitHubHostEnvVarSet returns true when at least one of the environment +// variables consulted by GetGitHubHost is explicitly set to a non-empty value. +// This indicates the caller has made an explicit host choice and automatic +// fallback heuristics (such as git-remote detection) should not be consulted. +func IsAnyGitHubHostEnvVarSet() bool { + for _, envVar := range []string{"GITHUB_SERVER_URL", "GITHUB_ENTERPRISE_HOST", "GITHUB_HOST", "GH_HOST"} { + if os.Getenv(envVar) != "" { //nolint:osgetenvlibrary + return true + } + } + return false +} + // GetGitHubHostForRepo returns the GitHub host URL for a specific repository. // Repositories under the github, githubnext, and microsoft organizations are // fetched from public GitHub (https://github.com) in cross-host contexts. From e768b677a89b2440b327dc1bf84e5c4b87330855 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 21 Jul 2026 04:54:05 +0000 Subject: [PATCH 6/9] Address code review: improve importAuthGHHost fallback and use TrimSuffix - importAuthGHHost: add manual strip fallback for the defensive parse-failure branch; switch from u.Host to u.Hostname() so port numbers are excluded from the returned hostname - detectGHESDeployment: replace strings.TrimRight(host, "/") with strings.TrimSuffix(host, "/") to remove exactly one trailing slash rather than all trailing '/' characters Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/cli/import_url_fetcher.go | 15 +++++++++++++-- pkg/cli/init.go | 2 +- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/pkg/cli/import_url_fetcher.go b/pkg/cli/import_url_fetcher.go index ccd304919bf..84fe5072d67 100644 --- a/pkg/cli/import_url_fetcher.go +++ b/pkg/cli/import_url_fetcher.go @@ -311,10 +311,21 @@ func importAuthGHHost() string { return "" } // getGitHubHost() returns a normalized https://… URL; parse just the hostname. - if u, parseErr := url.Parse(getGitHubHost()); parseErr == nil && u.Host != "" { + resolved := getGitHubHost() + if u, parseErr := url.Parse(resolved); parseErr == nil && u.Hostname() != "" { return strings.ToLower(u.Hostname()) } - return "" + // Fallback: strip scheme and path manually. getGitHubHost() always returns a + // well-formed URL, so this branch is only reached in unexpected edge cases. + bare := strings.TrimPrefix(resolved, "https://") + bare = strings.TrimPrefix(bare, "http://") + if idx := strings.IndexByte(bare, '/'); idx != -1 { + bare = bare[:idx] + } + if idx := strings.IndexByte(bare, ':'); idx != -1 { + bare = bare[:idx] + } + return strings.ToLower(bare) } // sanitizeHTTPError strips the request URL from a *url.Error (the error type diff --git a/pkg/cli/init.go b/pkg/cli/init.go index edd6ae4ec25..31915616001 100644 --- a/pkg/cli/init.go +++ b/pkg/cli/init.go @@ -349,7 +349,7 @@ func detectGHESDeployment() string { } host := strings.TrimPrefix(rawValue, "https://") host = strings.TrimPrefix(host, "http://") - host = strings.TrimRight(host, "/") + host = strings.TrimSuffix(host, "/") if isGHESHost(host) { initLog.Printf("Detected GHES deployment from %s: %s", envVar, host) return host From 0161a2b9a4afde894a0115b192369faabe14562b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 21 Jul 2026 04:57:50 +0000 Subject: [PATCH 7/9] =?UTF-8?q?fix(import=5Furl=5Ffetcher):=20simplify=20i?= =?UTF-8?q?mportAuthGHHost=20=E2=80=93=20log=20unexpected=20parse=20error,?= =?UTF-8?q?=20remove=20unreachable=20fallback?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/cli/import_url_fetcher.go | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/pkg/cli/import_url_fetcher.go b/pkg/cli/import_url_fetcher.go index 84fe5072d67..c1b7d5c46e3 100644 --- a/pkg/cli/import_url_fetcher.go +++ b/pkg/cli/import_url_fetcher.go @@ -310,22 +310,16 @@ func importAuthGHHost() string { if !isAnyGitHubHostEnvVarSet() { return "" } - // getGitHubHost() returns a normalized https://… URL; parse just the hostname. + // getGitHubHost (defined in this package) returns a normalized https://… URL; + // url.Parse is used only to extract the hostname so that port numbers are stripped. resolved := getGitHubHost() - if u, parseErr := url.Parse(resolved); parseErr == nil && u.Hostname() != "" { - return strings.ToLower(u.Hostname()) - } - // Fallback: strip scheme and path manually. getGitHubHost() always returns a - // well-formed URL, so this branch is only reached in unexpected edge cases. - bare := strings.TrimPrefix(resolved, "https://") - bare = strings.TrimPrefix(bare, "http://") - if idx := strings.IndexByte(bare, '/'); idx != -1 { - bare = bare[:idx] - } - if idx := strings.IndexByte(bare, ':'); idx != -1 { - bare = bare[:idx] + u, parseErr := url.Parse(resolved) + if parseErr != nil || u.Hostname() == "" { + // getGitHubHost always returns a well-formed URL; an error here is unexpected. + importURLFetcherLog.Printf("importAuthGHHost: unexpected url.Parse failure for %q: %v", resolved, parseErr) + return "" } - return strings.ToLower(bare) + return strings.ToLower(u.Hostname()) } // sanitizeHTTPError strips the request URL from a *url.Error (the error type From 800507332badc815768ef7f52f8683d237e63f5c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 21 Jul 2026 05:02:04 +0000 Subject: [PATCH 8/9] test(import_url_fetcher): add GITHUB_HOST env var test coverage Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/cli/import_url_fetcher_test.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/pkg/cli/import_url_fetcher_test.go b/pkg/cli/import_url_fetcher_test.go index ff59a5165e9..f2b69ce58fc 100644 --- a/pkg/cli/import_url_fetcher_test.go +++ b/pkg/cli/import_url_fetcher_test.go @@ -436,6 +436,20 @@ func TestAttachImportAuthHeader_GHE_ServerURLEnvVar(t *testing.T) { assert.Equal(t, "Bearer srv-token", req.Header.Get("Authorization"), "GITHUB_SERVER_URL must resolve as the auth host") } +// GITHUB_HOST resolves the auth host (third-highest priority, above GH_HOST). +func TestAttachImportAuthHeader_GHE_GitHubHostEnvVar(t *testing.T) { + t.Setenv("GITHUB_TOKEN", "gh-host-token") + t.Setenv("GH_TOKEN", "") + t.Setenv("GITHUB_SERVER_URL", "") + t.Setenv("GITHUB_ENTERPRISE_HOST", "") + t.Setenv("GITHUB_HOST", "ghhost.example.com") + t.Setenv("GH_HOST", "lowpriority.example.com") + + req, _ := http.NewRequest(http.MethodGet, "https://ghhost.example.com/owner/repo/raw/main/wf.md", nil) + attachImportAuthHeader(req, "https://ghhost.example.com/owner/repo/raw/main/wf.md") + assert.Equal(t, "Bearer "+"gh-host-token", req.Header.Get("Authorization"), "GITHUB_HOST must resolve as the auth host") +} + // TestBuildRequestLogString_RedactsAuthorization verifies that the request formatter // never exposes the raw token and shows the correct redacted form. func TestBuildRequestLogString_RedactsAuthorization(t *testing.T) { From 879957518bf58ded35d50664cdccbb31cb8ae636 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 21 Jul 2026 05:39:55 +0000 Subject: [PATCH 9/9] fix: revert git-remote auto-detection bypass to GH_HOST-only check The isAnyGitHubHostEnvVarSet() replacement broke three existing tests because GITHUB_SERVER_URL is always set in CI (to https://github.com), which caused the git-remote auto-detection block to be skipped entirely. The bypass condition should only apply to GH_HOST because: - GH_HOST is the env var the gh CLI uses to configure its target host - GITHUB_SERVER_URL/GITHUB_ENTERPRISE_HOST/GITHUB_HOST are informational automation vars that should not prevent local git remote detection Revert compile_orchestrator.go, setup_repository.go, and add_interactive_orchestrator.go to os.Getenv("GH_HOST") checks. Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com> --- pkg/cli/add_interactive_orchestrator.go | 2 +- pkg/cli/compile_orchestrator.go | 2 +- pkg/cli/setup_repository.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/cli/add_interactive_orchestrator.go b/pkg/cli/add_interactive_orchestrator.go index e89330a2528..866f6dc28cb 100644 --- a/pkg/cli/add_interactive_orchestrator.go +++ b/pkg/cli/add_interactive_orchestrator.go @@ -83,7 +83,7 @@ func RunAddInteractive(ctx context.Context, config *AddInteractiveConfig) error config.Ctx = ctx // Auto-detect GHES host from git remote if not already set - if !isAnyGitHubHostEnvVarSet() { + if os.Getenv("GH_HOST") == "" { //nolint:osgetenvlibrary detectedHost := getHostFromOriginRemote() if detectedHost != "github.com" { addInteractiveLog.Printf("Auto-detected GHES host from git remote: %s", detectedHost) diff --git a/pkg/cli/compile_orchestrator.go b/pkg/cli/compile_orchestrator.go index 7fac9bab8ee..411afcae16e 100644 --- a/pkg/cli/compile_orchestrator.go +++ b/pkg/cli/compile_orchestrator.go @@ -27,7 +27,7 @@ func CompileWorkflows(ctx context.Context, config CompileConfig) ([]*workflow.Wo default: } - if !isAnyGitHubHostEnvVarSet() { + if os.Getenv("GH_HOST") == "" { //nolint:osgetenvlibrary if detectedHost := getHostFromOriginRemote(); detectedHost != "github.com" && detectedHost != "" { compileOrchestratorLog.Printf("Auto-detected GHES host from git remote: %s", detectedHost) workflow.SetDefaultGHHost(detectedHost) diff --git a/pkg/cli/setup_repository.go b/pkg/cli/setup_repository.go index 7fc7431f2c5..bdda33189c4 100644 --- a/pkg/cli/setup_repository.go +++ b/pkg/cli/setup_repository.go @@ -18,7 +18,7 @@ import ( var setupRepositoryLog = logger.New("cli:setup_repository") func configureDefaultGHHostFromOriginRemoteIfUnset() { - if isAnyGitHubHostEnvVarSet() { + if os.Getenv("GH_HOST") != "" { //nolint:osgetenvlibrary return }