Skip to content

Fix cross-host workflow resolution in add and add-wizard when GH_HOST is a GHE instance#21349

Merged
pelikhan merged 5 commits intomainfrom
copilot/add-wizard-support-cross-org-queries
Mar 17, 2026
Merged

Fix cross-host workflow resolution in add and add-wizard when GH_HOST is a GHE instance#21349
pelikhan merged 5 commits intomainfrom
copilot/add-wizard-support-cross-org-queries

Conversation

Copy link
Contributor

Copilot AI commented Mar 17, 2026

When GH_HOST points to a GHE instance (e.g. contoso-aw.ghe.com), add/add-wizard with a full github.com URL incorrectly routed all API calls to the GHE instance, producing HTTP 404.

Root cause

api.DefaultRESTClient() and gh.Exec("api", ...) both honour GH_HOST. When the source URL explicitly names a different host, the default client targets the wrong endpoint.

Changes

pkg/cli/spec.go

  • Add Host string to WorkflowSpec — populated from the URL's hostname when a full URL is supplied.
  • Remove the github.com-only restriction in parseGitHubURL; GHE URLs (e.g. https://myorg.ghe.com/owner/repo/blob/...) are now accepted and their host captured.

pkg/parser/remote_fetch.go

  • Thread a host string parameter through all internal fetch/resolve functions.
  • When host != "", use api.NewRESTClient(api.ClientOptions{Host: host}) instead of api.DefaultRESTClient(), and pass --hostname <host> to gh.Exec("api", ...).
  • Git fallback functions (downloadFileViaGitClone, resolveRefToSHAViaGit) build the remote URL from the explicit host rather than GetGitHubHostForRepo.
  • downloadFileViaGit skips the raw.githubusercontent.com shortcut for non-github.com hosts (GHE has no raw-content equivalent).
  • resolveRemoteSymlinks now accepts the already-created *api.RESTClient from the caller instead of creating its own — avoids a second client targeting the wrong host.
  • New exports: DownloadFileFromGitHubForHost and ResolveRefToSHAForHost.

pkg/cli/fetch.go

  • fetchRemoteWorkflow calls the new host-aware functions when spec.Host is set.
# Before (GH_HOST=contoso-aw.ghe.com):
gh aw add https://github.com/githubnext/agentics/blob/main/workflows/q.md
# → HTTP 404 (https://api.contoso-aw.ghe.com/repos/githubnext/agentics/...)

# After:
# → fetches from api.github.com correctly

Copilot AI and others added 2 commits March 17, 2026 06:07
…ommands

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
…prove error message

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
@pelikhan pelikhan marked this pull request as ready for review March 17, 2026 06:13
Copilot AI review requested due to automatic review settings March 17, 2026 06:13
@pelikhan
Copy link
Contributor

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes incorrect API routing when GH_HOST points to a GitHub Enterprise instance but the workflow source URL explicitly targets a different host (e.g. github.com), ensuring add/add-wizard can fetch from the correct host.

Changes:

  • Add explicit Host tracking to WorkflowSpec when parsing full URLs.
  • Make remote ref resolution and file fetching host-aware (REST client host override + gh api --hostname).
  • Update call sites and tests to use the new host-aware behavior and the updated symlink-resolution signature.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
pkg/parser/remote_fetch.go Threads explicit host through ref resolution and download paths; reworks symlink resolution to reuse a caller-provided REST client; adds host-specific exported helpers.
pkg/parser/import_remote_nested_test.go Adjusts unit test call site for the new resolveRemoteSymlinks(*api.RESTClient, ...) signature.
pkg/cli/spec_test.go Extends workflow spec parsing tests to assert host parsing from full URLs.
pkg/cli/spec.go Adds Host to WorkflowSpec and captures hostname from parsed URLs (including mapping raw.githubusercontent.comgithub.com).
pkg/cli/fetch.go Switches remote workflow fetching to the new host-aware parser APIs based on spec.Host.
Comments suppressed due to low confidence (1)

pkg/parser/remote_fetch.go:272

  • downloadIncludeFromWorkflowSpec still hard-codes an empty host when resolving the ref for caching. With the new host-aware fetch path, this means workflows fetched from an explicit host (e.g. github.com while GH_HOST points to GHE) can still fail later when they import remote files via workflowspec, because include downloads/ref resolution will be routed to the configured default host. Consider plumbing the parent workflow’s host through ResolveIncludePath/downloadIncludeFromWorkflowSpec and using the new host-aware resolveRefToSHA/downloadFileFromGitHubWithDepth variants there as well.
	// Resolve ref to SHA for cache lookup
	var sha string
	if cache != nil {
		// Only resolve SHA if we're using the cache
		resolvedSHA, err := resolveRefToSHA(owner, repo, ref, "")
		if err != nil {
			// SHA resolution failure (including auth errors) only means we cannot cache; the
			// actual file download will be attempted below and may succeed via git fallback for
			// public repositories. Do not propagate this error - just skip caching.
			remoteLog.Printf("Failed to resolve ref to SHA, will skip cache: %v", err)
			// Continue without caching if SHA resolution fails
		} else {
			sha = resolvedSHA
			// Check cache using SHA
			if cachedPath, found := cache.Get(owner, repo, filePath, sha); found {
				remoteLog.Printf("Using cached import: %s/%s/%s@%s (SHA: %s)", owner, repo, filePath, ref, sha)
				return cachedPath, nil
			}
		}
	}

	// Download the file content from GitHub
	remoteLog.Printf("Fetching file from GitHub: %s/%s/%s@%s", owner, repo, filePath, ref)
	content, err := downloadFileFromGitHub(owner, repo, filePath, ref)
	if err != nil {

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

if spec.Version != tt.wantVersion {
t.Errorf("parseWorkflowSpec() version = %q, want %q", spec.Version, tt.wantVersion)
}
if tt.wantHost != "" && spec.Host != tt.wantHost {
Comment on lines 153 to +189
@@ -169,7 +168,7 @@ func parseGitHubURL(spec string) (*WorkflowSpec, error) {
return nil, err
}

specLog.Printf("Parsed GitHub URL: owner=%s, repo=%s, ref=%s, path=%s", owner, repo, ref, filePath)
specLog.Printf("Parsed GitHub URL: owner=%s, repo=%s, ref=%s, path=%s, host=%s", owner, repo, ref, filePath, parsedURL.Host)

// Ensure the file path ends with .md
if !strings.HasSuffix(filePath, ".md") {
@@ -181,13 +180,21 @@ func parseGitHubURL(spec string) (*WorkflowSpec, error) {
return nil, fmt.Errorf("invalid GitHub URL: '%s/%s' does not look like a valid GitHub repository", owner, repo)
}

// For raw.githubusercontent.com content, the API host is github.com.
// For all other hosts (github.com, GHE), use the URL's host as-is.
host := parsedURL.Host
if host == "raw.githubusercontent.com" {
host = "github.com"
}

Comment on lines +382 to +389
apiPath := fmt.Sprintf("/repos/%s/%s/commits/%s", owner, repo, ref)
var args []string
if host != "" {
args = []string{"api", "--hostname", host, apiPath, "--jq", ".sha"}
} else {
args = []string{"api", apiPath, "--jq", ".sha"}
}
stdout, stderr, err := gh.Exec(args...)
}
return "", fmt.Errorf("failed to create REST client: %w", err)
if client == nil {
return "", fmt.Errorf("no REST client available for symlink resolution of %s/%s/%s@%s", owner, repo, filePath, ref)
…re change

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Mar 17, 2026

@copilot fix tests https://github.com/github/gh-aw/actions/runs/23181075573/

Fixed in f1dfb80remote_fetch_integration_test.go also called resolveRemoteSymlinks with the old signature and wasn't caught by the unit-test build. Updated it to create a REST client and pass it as the first argument.

@pelikhan
Copy link
Contributor

…henticated

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Mar 17, 2026

@copilot fix tests https://github.com/github/gh-aw/actions/runs/23181320697/job/67395826085

Fixed in a1fb96dapi.DefaultRESTClient() itself was failing with "authentication token not found" in the unauthenticated CI runner, and the require.NoError stopped the test before skipOnAuthError could skip it. Changed to use skipOnAuthError on the client-creation error and return early.

@pelikhan
Copy link
Contributor

…like gitlab.com

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Mar 17, 2026

@copilot https://github.com/github/gh-aw/actions/runs/23193880964/job/67396916516

Fixed in e4b4aeaTestParseGitHubURL/invalid_-_non-github_domain was failing because I had removed the host check in parseGitHubURL. Re-added an isGitHubHost guard that allows github.com, raw.githubusercontent.com, *.ghe.com, and *.github.com, and rejects everything else (e.g. gitlab.com). Also updated the spec_test.go test case for gitlab.com to expect an error instead of success.

@pelikhan pelikhan merged commit f64dd91 into main Mar 17, 2026
53 checks passed
@pelikhan pelikhan deleted the copilot/add-wizard-support-cross-org-queries branch March 17, 2026 12:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants