diff --git a/.changeset/patch-repo-memory-ghe.md b/.changeset/patch-repo-memory-ghe.md new file mode 100644 index 0000000000..209b8a2c74 --- /dev/null +++ b/.changeset/patch-repo-memory-ghe.md @@ -0,0 +1,4 @@ +--- +"gh-aw": patch +--- +Fix repo-memory git URLs to respect the dynamic GitHub host so GHE workflows inherit the correct server URL instead of hardcoded github.com. diff --git a/.github/workflows/agent-performance-analyzer.lock.yml b/.github/workflows/agent-performance-analyzer.lock.yml index cdff26fe4f..f72ee181d7 100644 --- a/.github/workflows/agent-performance-analyzer.lock.yml +++ b/.github/workflows/agent-performance-analyzer.lock.yml @@ -155,6 +155,7 @@ jobs: - name: Clone repo-memory branch (default) env: GH_TOKEN: ${{ github.token }} + GITHUB_SERVER_URL: ${{ github.server_url }} BRANCH_NAME: memory/meta-orchestrators TARGET_REPO: ${{ github.repository }} MEMORY_DIR: /tmp/gh-aw/repo-memory/default @@ -1296,6 +1297,7 @@ jobs: env: GH_TOKEN: ${{ github.token }} GITHUB_RUN_ID: ${{ github.run_id }} + GITHUB_SERVER_URL: ${{ github.server_url }} ARTIFACT_DIR: /tmp/gh-aw/repo-memory/default MEMORY_ID: default TARGET_REPO: ${{ github.repository }} diff --git a/.github/workflows/audit-workflows.lock.yml b/.github/workflows/audit-workflows.lock.yml index b9f27b00fb..0bac1b663a 100644 --- a/.github/workflows/audit-workflows.lock.yml +++ b/.github/workflows/audit-workflows.lock.yml @@ -189,6 +189,7 @@ jobs: - name: Clone repo-memory branch (default) env: GH_TOKEN: ${{ github.token }} + GITHUB_SERVER_URL: ${{ github.server_url }} BRANCH_NAME: memory/audit-workflows TARGET_REPO: ${{ github.repository }} MEMORY_DIR: /tmp/gh-aw/repo-memory/default @@ -1326,6 +1327,7 @@ jobs: env: GH_TOKEN: ${{ github.token }} GITHUB_RUN_ID: ${{ github.run_id }} + GITHUB_SERVER_URL: ${{ github.server_url }} ARTIFACT_DIR: /tmp/gh-aw/repo-memory/default MEMORY_ID: default TARGET_REPO: ${{ github.repository }} diff --git a/actions/setup/js/push_repo_memory.cjs b/actions/setup/js/push_repo_memory.cjs index bbcd717b5b..f7e055dac4 100644 --- a/actions/setup/js/push_repo_memory.cjs +++ b/actions/setup/js/push_repo_memory.cjs @@ -59,6 +59,7 @@ async function main() { const ghToken = process.env.GH_TOKEN; const githubRunId = process.env.GITHUB_RUN_ID || "unknown"; + const githubServerUrl = process.env.GITHUB_SERVER_URL || "https://github.com"; // Log environment variable configuration for debugging core.info("Environment configuration:"); @@ -131,7 +132,9 @@ async function main() { // Checkout or create the memory branch core.info(`Checking out branch: ${branchName}...`); try { - const repoUrl = `https://x-access-token:${ghToken}@github.com/${targetRepo}.git`; + // Extract host from server URL (remove https:// prefix) + const serverHost = githubServerUrl.replace(/^https?:\/\//, ""); + const repoUrl = `https://x-access-token:${ghToken}@${serverHost}/${targetRepo}.git`; // Try to fetch the branch try { @@ -342,7 +345,9 @@ async function main() { // Pull with merge strategy (ours wins on conflicts) core.info(`Pulling latest changes from ${branchName}...`); try { - const repoUrl = `https://x-access-token:${ghToken}@github.com/${targetRepo}.git`; + // Extract host from server URL (remove https:// prefix) + const serverHost = githubServerUrl.replace(/^https?:\/\//, ""); + const repoUrl = `https://x-access-token:${ghToken}@${serverHost}/${targetRepo}.git`; execGitSync(["pull", "--no-rebase", "-X", "ours", repoUrl, branchName], { stdio: "inherit" }); } catch (error) { // Pull might fail if branch doesn't exist yet or on conflicts - this is acceptable @@ -352,7 +357,9 @@ async function main() { // Push changes core.info(`Pushing changes to ${branchName}...`); try { - const repoUrl = `https://x-access-token:${ghToken}@github.com/${targetRepo}.git`; + // Extract host from server URL (remove https:// prefix) + const serverHost = githubServerUrl.replace(/^https?:\/\//, ""); + const repoUrl = `https://x-access-token:${ghToken}@${serverHost}/${targetRepo}.git`; execGitSync(["push", repoUrl, `HEAD:${branchName}`], { stdio: "inherit" }); core.info(`Successfully pushed changes to ${branchName} branch`); } catch (error) { diff --git a/actions/setup/sh/clone_repo_memory_branch.sh b/actions/setup/sh/clone_repo_memory_branch.sh index 08b6fe9103..9c628d596f 100644 --- a/actions/setup/sh/clone_repo_memory_branch.sh +++ b/actions/setup/sh/clone_repo_memory_branch.sh @@ -8,6 +8,7 @@ # TARGET_REPO: Repository to clone from (e.g., owner/repo) # MEMORY_DIR: Directory to clone into # CREATE_ORPHAN: Whether to create orphan branch if it doesn't exist (true/false) +# GITHUB_SERVER_URL: GitHub server URL (e.g., https://github.com or https://ghe.company.com) set -e @@ -37,9 +38,18 @@ if [ -z "$CREATE_ORPHAN" ]; then exit 1 fi +# Default to github.com if not set +if [ -z "$GITHUB_SERVER_URL" ]; then + GITHUB_SERVER_URL="https://github.com" +fi + +# Extract host from server URL (remove https:// or http:// prefix) +SERVER_HOST="${GITHUB_SERVER_URL#https://}" +SERVER_HOST="${SERVER_HOST#http://}" + # Try to clone the branch (don't fail if it doesn't exist) set +e -git clone --depth 1 --single-branch --branch "$BRANCH_NAME" "https://x-access-token:${GH_TOKEN}@github.com/${TARGET_REPO}.git" "$MEMORY_DIR" 2>/dev/null +git clone --depth 1 --single-branch --branch "$BRANCH_NAME" "https://x-access-token:${GH_TOKEN}@${SERVER_HOST}/${TARGET_REPO}.git" "$MEMORY_DIR" 2>/dev/null CLONE_EXIT_CODE=$? set -e @@ -53,7 +63,7 @@ if [ $CLONE_EXIT_CODE -ne 0 ]; then git checkout --orphan "$BRANCH_NAME" git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" - git remote add origin "https://x-access-token:${GH_TOKEN}@github.com/${TARGET_REPO}.git" + git remote add origin "https://x-access-token:${GH_TOKEN}@${SERVER_HOST}/${TARGET_REPO}.git" else echo "Branch $BRANCH_NAME does not exist and create-orphan is false, skipping" mkdir -p "$MEMORY_DIR" diff --git a/pkg/workflow/repo_memory.go b/pkg/workflow/repo_memory.go index 201d2cec3a..f0d875a6f9 100644 --- a/pkg/workflow/repo_memory.go +++ b/pkg/workflow/repo_memory.go @@ -481,10 +481,14 @@ func generateRepoMemoryPushSteps(builder *strings.Builder, data *WorkflowData) { builder.WriteString(" if: always()\n") builder.WriteString(" env:\n") builder.WriteString(" GH_TOKEN: ${{ github.token }}\n") + builder.WriteString(" GITHUB_SERVER_URL: ${{ github.server_url }}\n") builder.WriteString(" run: |\n") builder.WriteString(" set -e\n") fmt.Fprintf(builder, " cd \"%s\" || exit 0\n", memoryDir) builder.WriteString(" \n") + builder.WriteString(" # Extract host from server URL (remove https:// prefix)\n") + builder.WriteString(" SERVER_HOST=\"${GITHUB_SERVER_URL#https://}\"\n") + builder.WriteString(" \n") builder.WriteString(" # Check if we have any changes to commit\n") builder.WriteString(" if [ -n \"$(git status --porcelain)\" ]; then\n") builder.WriteString(" echo \"Changes detected in repo memory, committing and pushing...\"\n") @@ -523,13 +527,13 @@ func generateRepoMemoryPushSteps(builder *strings.Builder, data *WorkflowData) { builder.WriteString(" \n") builder.WriteString(" # Pull with ours merge strategy (our changes win in conflicts)\n") builder.WriteString(" set +e\n") - fmt.Fprintf(builder, " git pull --no-rebase -s recursive -X ours \"https://x-access-token:${GH_TOKEN}@github.com/%s.git\" \"%s\" 2>&1\n", + fmt.Fprintf(builder, " git pull --no-rebase -s recursive -X ours \"https://x-access-token:${GH_TOKEN}@${SERVER_HOST}/%s.git\" \"%s\" 2>&1\n", targetRepo, memory.BranchName) builder.WriteString(" PULL_EXIT_CODE=$?\n") builder.WriteString(" set -e\n") builder.WriteString(" \n") builder.WriteString(" # Push changes (force push if needed due to conflict resolution)\n") - fmt.Fprintf(builder, " git push \"https://x-access-token:${GH_TOKEN}@github.com/%s.git\" \"HEAD:%s\"\n", + fmt.Fprintf(builder, " git push \"https://x-access-token:${GH_TOKEN}@${SERVER_HOST}/%s.git\" \"HEAD:%s\"\n", targetRepo, memory.BranchName) builder.WriteString(" \n") builder.WriteString(" echo \"Successfully pushed changes to repo memory\"\n") @@ -563,6 +567,7 @@ func generateRepoMemorySteps(builder *strings.Builder, data *WorkflowData) { fmt.Fprintf(builder, " - name: Clone repo-memory branch (%s)\n", memory.ID) builder.WriteString(" env:\n") builder.WriteString(" GH_TOKEN: ${{ github.token }}\n") + builder.WriteString(" GITHUB_SERVER_URL: ${{ github.server_url }}\n") fmt.Fprintf(builder, " BRANCH_NAME: %s\n", memory.BranchName) fmt.Fprintf(builder, " TARGET_REPO: %s\n", targetRepo) fmt.Fprintf(builder, " MEMORY_DIR: %s\n", memoryDir) @@ -650,6 +655,7 @@ func (c *Compiler) buildPushRepoMemoryJob(data *WorkflowData, threatDetectionEna step.WriteString(" env:\n") step.WriteString(" GH_TOKEN: ${{ github.token }}\n") step.WriteString(" GITHUB_RUN_ID: ${{ github.run_id }}\n") + step.WriteString(" GITHUB_SERVER_URL: ${{ github.server_url }}\n") fmt.Fprintf(&step, " ARTIFACT_DIR: %s\n", artifactDir) fmt.Fprintf(&step, " MEMORY_ID: %s\n", memory.ID) fmt.Fprintf(&step, " TARGET_REPO: %s\n", targetRepo) diff --git a/pkg/workflow/repo_memory_integration_test.go b/pkg/workflow/repo_memory_integration_test.go index 340c8dde1d..9f3b7fcb7a 100644 --- a/pkg/workflow/repo_memory_integration_test.go +++ b/pkg/workflow/repo_memory_integration_test.go @@ -306,3 +306,61 @@ This workflow has repo-memory disabled. t.Error("Should not have repo memory prompt when disabled") } } + +// TestRepoMemoryGitHubEnterpriseSupport tests that GITHUB_SERVER_URL is used for GHE compatibility +func TestRepoMemoryGitHubEnterpriseSupport(t *testing.T) { + tmpDir := testutil.TempDir(t, "test-*") + workflowPath := filepath.Join(tmpDir, "test-workflow.md") + + content := `--- +name: Test Repo Memory GHE +on: workflow_dispatch +engine: copilot +tools: + repo-memory: true +--- + +# Test Workflow + +This workflow tests GitHub Enterprise support. +` + + if err := os.WriteFile(workflowPath, []byte(content), 0644); err != nil { + t.Fatalf("Failed to write workflow file: %v", err) + } + + compiler := NewCompiler() + if err := compiler.CompileWorkflow(workflowPath); err != nil { + t.Fatalf("Failed to compile workflow: %v", err) + } + + // Read the generated lock file + lockPath := stringutil.MarkdownToLockFile(workflowPath) + lockContent, err := os.ReadFile(lockPath) + if err != nil { + t.Fatalf("Failed to read lock file: %v", err) + } + lockFile := string(lockContent) + + // Check that GITHUB_SERVER_URL is passed to clone step + if !strings.Contains(lockFile, "GITHUB_SERVER_URL: ${{ github.server_url }}") { + t.Error("Expected GITHUB_SERVER_URL environment variable in clone step") + } + + // Check that GITHUB_SERVER_URL is passed to push step (in push_repo_memory job) + // The push step should include GITHUB_SERVER_URL in the env section + if !strings.Contains(lockFile, "GITHUB_SERVER_URL: ${{ github.server_url }}") { + t.Error("Expected GITHUB_SERVER_URL environment variable in push step") + } + + // Verify that hardcoded github.com is NOT in git URLs + // The workflow should NOT contain hardcoded github.com URLs in git commands + if strings.Contains(lockFile, "@github.com/") { + t.Error("Found hardcoded @github.com in workflow - should use dynamic server URL") + } + + // Check for the shell script that uses GITHUB_SERVER_URL + if !strings.Contains(lockFile, "bash /opt/gh-aw/actions/clone_repo_memory_branch.sh") { + t.Error("Expected clone_repo_memory_branch.sh script invocation") + } +}