From 0b28d4bff5b72687d4d2d39d436927e3e9ba9bac Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 26 Oct 2025 09:23:36 +0000
Subject: [PATCH 1/8] Initial plan


From 82dc059cfbb884a823216243a8641f42ac715f97 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 26 Oct 2025 09:42:12 +0000
Subject: [PATCH 2/8] Standardize repository access with cached implementation

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
---
 pkg/cli/local_workflow_trial_test.go |  3 +
 pkg/cli/shared_utils.go              | 79 ++++++++++++++++++++---
 pkg/cli/shared_utils_test.go         | 96 ++++++++++++++++++++++++++++
 pkg/cli/spec_test.go                 |  3 +
 pkg/cli/trial_command.go             | 40 +-----------
 pkg/workflow/codex_engine.go         | 31 +--------
 pkg/workflow/codex_engine_test.go    |  4 +-
 pkg/workflow/copilot_engine.go       | 23 +------
 pkg/workflow/workflow_name.go        | 54 ++++++++++++++++
 9 files changed, 235 insertions(+), 98 deletions(-)
 create mode 100644 pkg/cli/shared_utils_test.go
 create mode 100644 pkg/workflow/workflow_name.go

diff --git a/pkg/cli/local_workflow_trial_test.go b/pkg/cli/local_workflow_trial_test.go
index 329b687c0a3..597f7faba1f 100644
--- a/pkg/cli/local_workflow_trial_test.go
+++ b/pkg/cli/local_workflow_trial_test.go
@@ -9,6 +9,9 @@ import (
 
 // TestLocalWorkflowTrialMode tests the local workflow installation for trial mode
 func TestLocalWorkflowTrialMode(t *testing.T) {
+	// Clear the repository slug cache to ensure clean test state
+	ClearCurrentRepoSlugCache()
+	
 	// Create a temporary directory for testing
 	tempDir, err := os.MkdirTemp("", "gh-aw-local-trial-test-*")
 	if err != nil {
diff --git a/pkg/cli/shared_utils.go b/pkg/cli/shared_utils.go
index 361e70127f5..3ab43292ad2 100644
--- a/pkg/cli/shared_utils.go
+++ b/pkg/cli/shared_utils.go
@@ -6,11 +6,27 @@ import (
 	"os"
 	"os/exec"
 	"strings"
+	"sync"
 	"time"
 
 	"github.com/githubnext/gh-aw/pkg/console"
 )
 
+// Global cache for current repository info
+var (
+	getCurrentRepoSlugOnce   sync.Once
+	currentRepoSlugResult    string
+	currentRepoSlugError     error
+)
+
+// ClearCurrentRepoSlugCache clears the current repository slug cache
+// This is useful for testing or when repository context might have changed
+func ClearCurrentRepoSlugCache() {
+	getCurrentRepoSlugOnce = sync.Once{}
+	currentRepoSlugResult = ""
+	currentRepoSlugError = nil
+}
+
 // PullRequest represents a GitHub Pull Request
 type PullRequest struct {
 	Number    int       `json:"number"`
@@ -160,24 +176,71 @@ func WaitForWorkflowCompletion(repoSlug, runID string, timeoutMinutes int, verbo
 	}
 }
 
-// GetCurrentRepoSlug gets the current repository slug (owner/repo) using gh CLI
+// GetCurrentRepoSlug gets the current repository slug (owner/repo) using gh CLI (uncached)
+// Falls back to git remote parsing if gh CLI is not available
 func GetCurrentRepoSlug() (string, error) {
+	// Try gh CLI first (most reliable)
 	cmd := exec.Command("gh", "repo", "view", "--json", "owner,name", "--jq", ".owner.login + \"/\" + .name")
 	output, err := cmd.Output()
+	if err == nil {
+		repoSlug := strings.TrimSpace(string(output))
+		if repoSlug != "" {
+			// Validate format (should be owner/repo)
+			parts := strings.Split(repoSlug, "/")
+			if len(parts) == 2 && parts[0] != "" && parts[1] != "" {
+				return repoSlug, nil
+			}
+		}
+	}
+
+	// Fallback to git remote parsing if gh CLI is not available or fails
+	cmd = exec.Command("git", "remote", "get-url", "origin")
+	output, err = cmd.Output()
 	if err != nil {
-		return "", fmt.Errorf("failed to get current repository: %w", err)
+		return "", fmt.Errorf("failed to get current repository (gh CLI and git remote both failed): %w", err)
 	}
 
-	repoSlug := strings.TrimSpace(string(output))
-	if repoSlug == "" {
-		return "", fmt.Errorf("repository slug is empty")
+	remoteURL := strings.TrimSpace(string(output))
+
+	// Parse GitHub repository from remote URL
+	// Handle both SSH and HTTPS formats
+	var repoPath string
+
+	// SSH format: git@github.com:owner/repo.git
+	if strings.HasPrefix(remoteURL, "git@github.com:") {
+		repoPath = strings.TrimPrefix(remoteURL, "git@github.com:")
+	} else if strings.Contains(remoteURL, "github.com/") {
+		// HTTPS format: https://github.com/owner/repo.git
+		parts := strings.Split(remoteURL, "github.com/")
+		if len(parts) >= 2 {
+			repoPath = parts[1]
+		}
+	} else {
+		return "", fmt.Errorf("remote URL does not appear to be a GitHub repository: %s", remoteURL)
 	}
 
+	// Remove .git suffix if present
+	repoPath = strings.TrimSuffix(repoPath, ".git")
+
 	// Validate format (should be owner/repo)
-	parts := strings.Split(repoSlug, "/")
+	parts := strings.Split(repoPath, "/")
 	if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
-		return "", fmt.Errorf("invalid repository format: %s", repoSlug)
+		return "", fmt.Errorf("invalid repository format: %s", repoPath)
+	}
+
+	return repoPath, nil
+}
+
+// GetCurrentRepoSlugCached gets the current repository slug with caching using sync.Once
+// This is the recommended function to use for repository access across the codebase
+func GetCurrentRepoSlugCached() (string, error) {
+	getCurrentRepoSlugOnce.Do(func() {
+		currentRepoSlugResult, currentRepoSlugError = GetCurrentRepoSlug()
+	})
+
+	if currentRepoSlugError != nil {
+		return "", currentRepoSlugError
 	}
 
-	return repoSlug, nil
+	return currentRepoSlugResult, nil
 }
diff --git a/pkg/cli/shared_utils_test.go b/pkg/cli/shared_utils_test.go
new file mode 100644
index 00000000000..6a84c374ead
--- /dev/null
+++ b/pkg/cli/shared_utils_test.go
@@ -0,0 +1,96 @@
+package cli
+
+import (
+	"testing"
+)
+
+// TestGetCurrentRepoSlugCached tests that the cached version calls the underlying function only once
+func TestGetCurrentRepoSlugCached(t *testing.T) {
+	// Clear the cache before testing
+	ClearCurrentRepoSlugCache()
+
+	// Note: In a real-world scenario, we would use dependency injection or interfaces
+	// For this test, we'll just verify that multiple calls to GetCurrentRepoSlugCached
+	// produce the same result (which implies caching is working)
+	
+	// First call
+	result1, err1 := GetCurrentRepoSlugCached()
+	if err1 != nil {
+		t.Logf("First call error (expected if not in a git repo): %v", err1)
+	}
+	
+	// Second call
+	result2, err2 := GetCurrentRepoSlugCached()
+	if err2 != nil {
+		t.Logf("Second call error (expected if not in a git repo): %v", err2)
+	}
+	
+	// Both calls should return the same result and same error
+	if result1 != result2 {
+		t.Errorf("GetCurrentRepoSlugCached returned different results on multiple calls: %s vs %s", result1, result2)
+	}
+	
+	if (err1 == nil) != (err2 == nil) {
+		t.Errorf("GetCurrentRepoSlugCached returned different error states on multiple calls")
+	}
+	
+	if err1 != nil && err2 != nil && err1.Error() != err2.Error() {
+		t.Errorf("GetCurrentRepoSlugCached returned different errors on multiple calls: %v vs %v", err1, err2)
+	}
+	
+	t.Logf("GetCurrentRepoSlugCached returned consistent results (result: %s, error: %v)", result1, err1)
+}
+
+// TestClearCurrentRepoSlugCache tests that clearing the cache allows a fresh call
+func TestClearCurrentRepoSlugCache(t *testing.T) {
+	// Clear the cache
+	ClearCurrentRepoSlugCache()
+	
+	// First call
+	result1, err1 := GetCurrentRepoSlugCached()
+	if err1 != nil {
+		t.Logf("First call error (expected if not in a git repo): %v", err1)
+	}
+	
+	// Clear the cache again
+	ClearCurrentRepoSlugCache()
+	
+	// Second call after clearing cache
+	result2, err2 := GetCurrentRepoSlugCached()
+	if err2 != nil {
+		t.Logf("Second call error (expected if not in a git repo): %v", err2)
+	}
+	
+	// Results should still be the same (we're in the same repo)
+	if result1 != result2 {
+		t.Errorf("GetCurrentRepoSlugCached returned different results after cache clear: %s vs %s", result1, result2)
+	}
+	
+	if (err1 == nil) != (err2 == nil) {
+		t.Errorf("GetCurrentRepoSlugCached returned different error states after cache clear")
+	}
+	
+	t.Logf("Cache clear test passed (result: %s, error: %v)", result1, err1)
+}
+
+// TestGetCurrentRepoSlugFormat tests the format validation
+func TestGetCurrentRepoSlugFormat(t *testing.T) {
+	// This test will only pass if we're in a GitHub repository
+	result, err := GetCurrentRepoSlug()
+	
+	if err != nil {
+		t.Logf("GetCurrentRepoSlug returned error (expected if not in a git repo): %v", err)
+		// Skip further validation if we're not in a repo
+		return
+	}
+	
+	// Verify the format is "owner/repo"
+	if result == "" {
+		t.Error("GetCurrentRepoSlug returned empty string without error")
+		return
+	}
+	
+	// The function already validates the format, so if we got here without error,
+	// the format should be valid. Let's just log the result.
+	t.Logf("GetCurrentRepoSlug returned: %s", result)
+}
diff --git a/pkg/cli/spec_test.go b/pkg/cli/spec_test.go
index 1c2a54f2723..12577e196a0 100644
--- a/pkg/cli/spec_test.go
+++ b/pkg/cli/spec_test.go
@@ -331,6 +331,9 @@ func TestParseWorkflowSpec(t *testing.T) {
 }
 
 func TestParseLocalWorkflowSpec(t *testing.T) {
+	// Clear the repository slug cache to ensure clean test state
+	ClearCurrentRepoSlugCache()
+	
 	tests := []struct {
 		name             string
 		spec             string
diff --git a/pkg/cli/trial_command.go b/pkg/cli/trial_command.go
index d4c6b82d7c5..7966007f2a5 100644
--- a/pkg/cli/trial_command.go
+++ b/pkg/cli/trial_command.go
@@ -440,44 +440,10 @@ func RunWorkflowTrials(workflowSpecs []string, logicalRepoSpec string, cloneRepo
 
 }
 
-// getCurrentRepositoryInfo determines the current repository from git remote
+// getCurrentRepositoryInfo determines the current repository from the gh CLI (cached)
+// This is a wrapper around GetCurrentRepoSlugCached for backward compatibility
 func getCurrentRepositoryInfo() (string, error) {
-	// Get git remote URL
-	cmd := exec.Command("git", "remote", "get-url", "origin")
-	output, err := cmd.Output()
-	if err != nil {
-		return "", fmt.Errorf("failed to get git remote origin: %w", err)
-	}
-
-	remoteURL := strings.TrimSpace(string(output))
-
-	// Parse GitHub repository from remote URL
-	// Handle both SSH and HTTPS formats
-	var repoPath string
-
-	// SSH format: git@github.com:owner/repo.git
-	if strings.HasPrefix(remoteURL, "git@github.com:") {
-		repoPath = strings.TrimPrefix(remoteURL, "git@github.com:")
-	} else if strings.Contains(remoteURL, "github.com/") {
-		// HTTPS format: https://github.com/owner/repo.git
-		parts := strings.Split(remoteURL, "github.com/")
-		if len(parts) >= 2 {
-			repoPath = parts[1]
-		}
-	} else {
-		return "", fmt.Errorf("remote URL does not appear to be a GitHub repository: %s", remoteURL)
-	}
-
-	// Remove .git suffix if present
-	repoPath = strings.TrimSuffix(repoPath, ".git")
-
-	// Use parseRepoSpec to validate the format consistently
-	repoSpec, err := parseRepoSpec(repoPath)
-	if err != nil {
-		return "", fmt.Errorf("invalid repository format from git remote: %w", err)
-	}
-
-	return repoSpec.RepoSlug, nil
+	return GetCurrentRepoSlugCached()
 }
 
 // getCurrentGitHubUsername gets the current GitHub username from gh CLI
diff --git a/pkg/workflow/codex_engine.go b/pkg/workflow/codex_engine.go
index 5010d2635e2..ad3f543e921 100644
--- a/pkg/workflow/codex_engine.go
+++ b/pkg/workflow/codex_engine.go
@@ -11,12 +11,6 @@ import (
 	"github.com/githubnext/gh-aw/pkg/constants"
 )
 
-// Pre-compiled regexes for convertToIdentifier (performance optimization)
-var (
-	identifierNonAlphanumeric = regexp.MustCompile(`[^a-z0-9-]`)
-	identifierMultipleHyphens = regexp.MustCompile(`-+`)
-)
-
 // Pre-compiled regexes for Codex log parsing (performance optimization)
 var (
 	codexToolCallOldFormat    = regexp.MustCompile(`\] tool ([^(]+)\(`)
@@ -26,29 +20,6 @@ var (
 	codexDurationPattern      = regexp.MustCompile(`in\s+(\d+(?:\.\d+)?)\s*s`)
 )
 
-// convertToIdentifier converts a workflow name to a valid identifier format
-// by converting to lowercase and replacing spaces with hyphens
-func convertToIdentifier(name string) string {
-	// Convert to lowercase
-	identifier := strings.ToLower(name)
-	// Replace spaces and other common separators with hyphens
-	identifier = strings.ReplaceAll(identifier, " ", "-")
-	identifier = strings.ReplaceAll(identifier, "_", "-")
-	// Remove any characters that aren't alphanumeric or hyphens
-	identifier = identifierNonAlphanumeric.ReplaceAllString(identifier, "")
-	// Remove any double hyphens that might have been created
-	identifier = identifierMultipleHyphens.ReplaceAllString(identifier, "-")
-	// Remove leading/trailing hyphens
-	identifier = strings.Trim(identifier, "-")
-
-	// If the result is empty, return a default identifier
-	if identifier == "" {
-		identifier = "github-agentic-workflow"
-	}
-
-	return identifier
-}
-
 // CodexEngine represents the Codex agentic engine (experimental)
 type CodexEngine struct {
 	BaseEngine
@@ -509,7 +480,7 @@ func (e *CodexEngine) renderGitHubCodexMCPConfig(yaml *strings.Builder, githubTo
 			userAgent = workflowData.EngineConfig.UserAgent
 		} else if workflowData.Name != "" {
 			// Fall back to converting workflow name to identifier
-			userAgent = convertToIdentifier(workflowData.Name)
+			userAgent = ConvertToIdentifier(workflowData.Name)
 		}
 	}
 	yaml.WriteString("          user_agent = \"" + userAgent + "\"\n")
diff --git a/pkg/workflow/codex_engine_test.go b/pkg/workflow/codex_engine_test.go
index 74c4721234e..59964a4df7a 100644
--- a/pkg/workflow/codex_engine_test.go
+++ b/pkg/workflow/codex_engine_test.go
@@ -544,9 +544,9 @@ func TestConvertToIdentifier(t *testing.T) {
 
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			result := convertToIdentifier(tt.input)
+			result := ConvertToIdentifier(tt.input)
 			if result != tt.expected {
-				t.Errorf("convertToIdentifier(%q) = %q, expected %q", tt.input, result, tt.expected)
+				t.Errorf("ConvertToIdentifier(%q) = %q, expected %q", tt.input, result, tt.expected)
 			}
 		})
 	}
diff --git a/pkg/workflow/copilot_engine.go b/pkg/workflow/copilot_engine.go
index 5e50e1d4a5f..21b2287baaa 100644
--- a/pkg/workflow/copilot_engine.go
+++ b/pkg/workflow/copilot_engine.go
@@ -900,28 +900,9 @@ func generateAWFCleanupStep(scriptPath string) GitHubActionStep {
 	return GitHubActionStep(stepLines)
 }
 
-// sanitizeWorkflowName sanitizes a workflow name for use in artifact names and file paths
-// Removes or replaces characters that are invalid in YAML artifact names or filesystem paths
-func sanitizeWorkflowName(name string) string {
-	// Replace colons, slashes, and other problematic characters with hyphens
-	sanitized := strings.ReplaceAll(name, ":", "-")
-	sanitized = strings.ReplaceAll(sanitized, "/", "-")
-	sanitized = strings.ReplaceAll(sanitized, "\\", "-")
-	sanitized = strings.ReplaceAll(sanitized, " ", "-")
-	// Remove any remaining special characters that might cause issues
-	sanitized = strings.Map(func(r rune) rune {
-		// Allow alphanumeric, hyphens, underscores, and periods
-		if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == '-' || r == '_' || r == '.' {
-			return r
-		}
-		return '-'
-	}, sanitized)
-	return sanitized
-}
-
 // generateSquidLogsCollectionStep creates a GitHub Actions step to collect Squid logs from AWF
 func generateSquidLogsCollectionStep(workflowName string) GitHubActionStep {
-	sanitizedName := strings.ToLower(sanitizeWorkflowName(workflowName))
+	sanitizedName := strings.ToLower(SanitizeWorkflowName(workflowName))
 	squidLogsDir := fmt.Sprintf("/tmp/gh-aw/squid-logs-%s/", sanitizedName)
 
 	stepLines := []string{
@@ -943,7 +924,7 @@ func generateSquidLogsCollectionStep(workflowName string) GitHubActionStep {
 
 // generateSquidLogsUploadStep creates a GitHub Actions step to upload Squid logs as artifact
 func generateSquidLogsUploadStep(workflowName string) GitHubActionStep {
-	sanitizedName := strings.ToLower(sanitizeWorkflowName(workflowName))
+	sanitizedName := strings.ToLower(SanitizeWorkflowName(workflowName))
 	artifactName := fmt.Sprintf("squid-logs-%s", sanitizedName)
 	squidLogsDir := fmt.Sprintf("/tmp/gh-aw/squid-logs-%s/", sanitizedName)
 
diff --git a/pkg/workflow/workflow_name.go b/pkg/workflow/workflow_name.go
new file mode 100644
index 00000000000..6936236b3ef
--- /dev/null
+++ b/pkg/workflow/workflow_name.go
@@ -0,0 +1,54 @@
+package workflow
+
+import (
+	"regexp"
+	"strings"
+)
+
+var (
+	// Regular expressions for workflow name sanitization
+	identifierNonAlphanumeric  = regexp.MustCompile(`[^a-z0-9-]`)
+	identifierMultipleHyphens  = regexp.MustCompile(`-+`)
+)
+
+// SanitizeWorkflowName sanitizes a workflow name for use in artifact names and file paths
+// Removes or replaces characters that are invalid in YAML artifact names or filesystem paths
+func SanitizeWorkflowName(name string) string {
+	// Replace colons, slashes, and other problematic characters with hyphens
+	sanitized := strings.ReplaceAll(name, ":", "-")
+	sanitized = strings.ReplaceAll(sanitized, "/", "-")
+	sanitized = strings.ReplaceAll(sanitized, "\\", "-")
+	sanitized = strings.ReplaceAll(sanitized, " ", "-")
+	// Remove any remaining special characters that might cause issues
+	sanitized = strings.Map(func(r rune) rune {
+		// Allow alphanumeric, hyphens, underscores, and periods
+		if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == '-' || r == '_' || r == '.' {
+			return r
+		}
+		return '-'
+	}, sanitized)
+	return sanitized
+}
+
+// ConvertToIdentifier converts a workflow name to a valid identifier format
+// by converting to lowercase and replacing spaces with hyphens
+func ConvertToIdentifier(name string) string {
+	// Convert to lowercase
+	identifier := strings.ToLower(name)
+	// Replace spaces and other common separators with hyphens
+	identifier = strings.ReplaceAll(identifier, " ", "-")
+	identifier = strings.ReplaceAll(identifier, "_", "-")
+	// Remove any characters that aren't alphanumeric or hyphens
+	identifier = identifierNonAlphanumeric.ReplaceAllString(identifier, "")
+	// Remove any double hyphens that might have been created
+	identifier = identifierMultipleHyphens.ReplaceAllString(identifier, "-")
+	// Remove leading/trailing hyphens
+	identifier = strings.Trim(identifier, "-")
+
+	// If the result is empty, return a default identifier
+	if identifier == "" {
+		identifier = "github-agentic-workflow"
+	}
+
+	return identifier
+}

From d4faaa57adcec746539e2b94456538dd6400cc8b Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 26 Oct 2025 09:46:32 +0000
Subject: [PATCH 3/8] Run make fmt to fix formatting

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
---
 .../docs/reference/frontmatter-full.md        |  4 +++
 pkg/cli/local_workflow_trial_test.go          |  2 +-
 pkg/cli/shared_utils.go                       |  6 ++--
 pkg/cli/shared_utils_test.go                  | 30 +++++++++----------
 pkg/cli/spec_test.go                          |  2 +-
 pkg/workflow/workflow_name.go                 |  4 +--
 6 files changed, 26 insertions(+), 22 deletions(-)

diff --git a/docs/src/content/docs/reference/frontmatter-full.md b/docs/src/content/docs/reference/frontmatter-full.md
index de7973f28a9..5dadda254c4 100644
--- a/docs/src/content/docs/reference/frontmatter-full.md
+++ b/docs/src/content/docs/reference/frontmatter-full.md
@@ -791,6 +791,10 @@ network:
     # (optional)
     version: "example-value"
 
+    # AWF log level (default: info). Valid values: debug, info, warn, error
+    # (optional)
+    log-level: "debug"
+
 # Conditional execution expression
 # (optional)
 if: "example-value"
diff --git a/pkg/cli/local_workflow_trial_test.go b/pkg/cli/local_workflow_trial_test.go
index 597f7faba1f..5ec79d17855 100644
--- a/pkg/cli/local_workflow_trial_test.go
+++ b/pkg/cli/local_workflow_trial_test.go
@@ -11,7 +11,7 @@ import (
 func TestLocalWorkflowTrialMode(t *testing.T) {
 	// Clear the repository slug cache to ensure clean test state
 	ClearCurrentRepoSlugCache()
-	
+
 	// Create a temporary directory for testing
 	tempDir, err := os.MkdirTemp("", "gh-aw-local-trial-test-*")
 	if err != nil {
diff --git a/pkg/cli/shared_utils.go b/pkg/cli/shared_utils.go
index 3ab43292ad2..e7d30fc881b 100644
--- a/pkg/cli/shared_utils.go
+++ b/pkg/cli/shared_utils.go
@@ -14,9 +14,9 @@ import (
 
 // Global cache for current repository info
 var (
-	getCurrentRepoSlugOnce   sync.Once
-	currentRepoSlugResult    string
-	currentRepoSlugError     error
+	getCurrentRepoSlugOnce sync.Once
+	currentRepoSlugResult  string
+	currentRepoSlugError   error
 )
 
 // ClearCurrentRepoSlugCache clears the current repository slug cache
diff --git a/pkg/cli/shared_utils_test.go b/pkg/cli/shared_utils_test.go
index 6a84c374ead..f1c45127c8a 100644
--- a/pkg/cli/shared_utils_test.go
+++ b/pkg/cli/shared_utils_test.go
@@ -12,32 +12,32 @@ func TestGetCurrentRepoSlugCached(t *testing.T) {
 	// Note: In a real-world scenario, we would use dependency injection or interfaces
 	// For this test, we'll just verify that multiple calls to GetCurrentRepoSlugCached
 	// produce the same result (which implies caching is working)
-	
+
 	// First call
 	result1, err1 := GetCurrentRepoSlugCached()
 	if err1 != nil {
 		t.Logf("First call error (expected if not in a git repo): %v", err1)
 	}
-	
+
 	// Second call
 	result2, err2 := GetCurrentRepoSlugCached()
 	if err2 != nil {
 		t.Logf("Second call error (expected if not in a git repo): %v", err2)
 	}
-	
+
 	// Both calls should return the same result and same error
 	if result1 != result2 {
 		t.Errorf("GetCurrentRepoSlugCached returned different results on multiple calls: %s vs %s", result1, result2)
 	}
-	
+
 	if (err1 == nil) != (err2 == nil) {
 		t.Errorf("GetCurrentRepoSlugCached returned different error states on multiple calls")
 	}
-	
+
 	if err1 != nil && err2 != nil && err1.Error() != err2.Error() {
 		t.Errorf("GetCurrentRepoSlugCached returned different errors on multiple calls: %v vs %v", err1, err2)
 	}
-	
+
 	t.Logf("GetCurrentRepoSlugCached returned consistent results (result: %s, error: %v)", result1, err1)
 }
 
@@ -45,31 +45,31 @@ func TestGetCurrentRepoSlugCached(t *testing.T) {
 func TestClearCurrentRepoSlugCache(t *testing.T) {
 	// Clear the cache
 	ClearCurrentRepoSlugCache()
-	
+
 	// First call
 	result1, err1 := GetCurrentRepoSlugCached()
 	if err1 != nil {
 		t.Logf("First call error (expected if not in a git repo): %v", err1)
 	}
-	
+
 	// Clear the cache again
 	ClearCurrentRepoSlugCache()
-	
+
 	// Second call after clearing cache
 	result2, err2 := GetCurrentRepoSlugCached()
 	if err2 != nil {
 		t.Logf("Second call error (expected if not in a git repo): %v", err2)
 	}
-	
+
 	// Results should still be the same (we're in the same repo)
 	if result1 != result2 {
 		t.Errorf("GetCurrentRepoSlugCached returned different results after cache clear: %s vs %s", result1, result2)
 	}
-	
+
 	if (err1 == nil) != (err2 == nil) {
 		t.Errorf("GetCurrentRepoSlugCached returned different error states after cache clear")
 	}
-	
+
 	t.Logf("Cache clear test passed (result: %s, error: %v)", result1, err1)
 }
 
@@ -77,19 +77,19 @@ func TestClearCurrentRepoSlugCache(t *testing.T) {
 func TestGetCurrentRepoSlugFormat(t *testing.T) {
 	// This test will only pass if we're in a GitHub repository
 	result, err := GetCurrentRepoSlug()
-	
+
 	if err != nil {
 		t.Logf("GetCurrentRepoSlug returned error (expected if not in a git repo): %v", err)
 		// Skip further validation if we're not in a repo
 		return
 	}
-	
+
 	// Verify the format is "owner/repo"
 	if result == "" {
 		t.Error("GetCurrentRepoSlug returned empty string without error")
 		return
 	}
-	
+
 	// The function already validates the format, so if we got here without error,
 	// the format should be valid. Let's just log the result.
 	t.Logf("GetCurrentRepoSlug returned: %s", result)
diff --git a/pkg/cli/spec_test.go b/pkg/cli/spec_test.go
index 12577e196a0..19269e7246e 100644
--- a/pkg/cli/spec_test.go
+++ b/pkg/cli/spec_test.go
@@ -333,7 +333,7 @@ func TestParseWorkflowSpec(t *testing.T) {
 func TestParseLocalWorkflowSpec(t *testing.T) {
 	// Clear the repository slug cache to ensure clean test state
 	ClearCurrentRepoSlugCache()
-	
+
 	tests := []struct {
 		name             string
 		spec             string
diff --git a/pkg/workflow/workflow_name.go b/pkg/workflow/workflow_name.go
index 6936236b3ef..50c9c9851b3 100644
--- a/pkg/workflow/workflow_name.go
+++ b/pkg/workflow/workflow_name.go
@@ -7,8 +7,8 @@ import (
 
 var (
 	// Regular expressions for workflow name sanitization
-	identifierNonAlphanumeric  = regexp.MustCompile(`[^a-z0-9-]`)
-	identifierMultipleHyphens  = regexp.MustCompile(`-+`)
+	identifierNonAlphanumeric = regexp.MustCompile(`[^a-z0-9-]`)
+	identifierMultipleHyphens = regexp.MustCompile(`-+`)
 )
 
 // SanitizeWorkflowName sanitizes a workflow name for use in artifact names and file paths

From 40cc026f8c4d095445dca2fe4905a7b645c9650e Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 26 Oct 2025 09:57:46 +0000
Subject: [PATCH 4/8] Move repository slug logic to repo.go

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
---
 pkg/cli/repo.go                               | 92 +++++++++++++++++++
 .../{shared_utils_test.go => repo_test.go}    |  0
 pkg/cli/shared_utils.go                       | 85 -----------------
 3 files changed, 92 insertions(+), 85 deletions(-)
 create mode 100644 pkg/cli/repo.go
 rename pkg/cli/{shared_utils_test.go => repo_test.go} (100%)

diff --git a/pkg/cli/repo.go b/pkg/cli/repo.go
new file mode 100644
index 00000000000..4aff2033107
--- /dev/null
+++ b/pkg/cli/repo.go
@@ -0,0 +1,92 @@
+package cli
+
+import (
+	"fmt"
+	"os/exec"
+	"strings"
+	"sync"
+)
+
+// Global cache for current repository info
+var (
+	getCurrentRepoSlugOnce sync.Once
+	currentRepoSlugResult  string
+	currentRepoSlugError   error
+)
+
+// ClearCurrentRepoSlugCache clears the current repository slug cache
+// This is useful for testing or when repository context might have changed
+func ClearCurrentRepoSlugCache() {
+	getCurrentRepoSlugOnce = sync.Once{}
+	currentRepoSlugResult = ""
+	currentRepoSlugError = nil
+}
+
+// GetCurrentRepoSlug gets the current repository slug (owner/repo) using gh CLI (uncached)
+// Falls back to git remote parsing if gh CLI is not available
+func GetCurrentRepoSlug() (string, error) {
+	// Try gh CLI first (most reliable)
+	cmd := exec.Command("gh", "repo", "view", "--json", "owner,name", "--jq", ".owner.login + \"/\" + .name")
+	output, err := cmd.Output()
+	if err == nil {
+		repoSlug := strings.TrimSpace(string(output))
+		if repoSlug != "" {
+			// Validate format (should be owner/repo)
+			parts := strings.Split(repoSlug, "/")
+			if len(parts) == 2 && parts[0] != "" && parts[1] != "" {
+				return repoSlug, nil
+			}
+		}
+	}
+
+	// Fallback to git remote parsing if gh CLI is not available or fails
+	cmd = exec.Command("git", "remote", "get-url", "origin")
+	output, err = cmd.Output()
+	if err != nil {
+		return "", fmt.Errorf("failed to get current repository (gh CLI and git remote both failed): %w", err)
+	}
+
+	remoteURL := strings.TrimSpace(string(output))
+
+	// Parse GitHub repository from remote URL
+	// Handle both SSH and HTTPS formats
+	var repoPath string
+
+	// SSH format: git@github.com:owner/repo.git
+	if strings.HasPrefix(remoteURL, "git@github.com:") {
+		repoPath = strings.TrimPrefix(remoteURL, "git@github.com:")
+	} else if strings.Contains(remoteURL, "github.com/") {
+		// HTTPS format: https://github.com/owner/repo.git
+		parts := strings.Split(remoteURL, "github.com/")
+		if len(parts) >= 2 {
+			repoPath = parts[1]
+		}
+	} else {
+		return "", fmt.Errorf("remote URL does not appear to be a GitHub repository: %s", remoteURL)
+	}
+
+	// Remove .git suffix if present
+	repoPath = strings.TrimSuffix(repoPath, ".git")
+
+	// Validate format (should be owner/repo)
+	parts := strings.Split(repoPath, "/")
+	if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
+		return "", fmt.Errorf("invalid repository format: %s", repoPath)
+	}
+
+	return repoPath, nil
+}
+
+// GetCurrentRepoSlugCached gets the current repository slug with caching using sync.Once
+// This is the recommended function to use for repository access across the codebase
+func GetCurrentRepoSlugCached() (string, error) {
+	getCurrentRepoSlugOnce.Do(func() {
+		currentRepoSlugResult, currentRepoSlugError = GetCurrentRepoSlug()
+	})
+
+	if currentRepoSlugError != nil {
+		return "", currentRepoSlugError
+	}
+
+	return currentRepoSlugResult, nil
+}
diff --git a/pkg/cli/shared_utils_test.go b/pkg/cli/repo_test.go
similarity index 100%
rename from pkg/cli/shared_utils_test.go
rename to pkg/cli/repo_test.go
diff --git a/pkg/cli/shared_utils.go b/pkg/cli/shared_utils.go
index e7d30fc881b..98036fe986e 100644
--- a/pkg/cli/shared_utils.go
+++ b/pkg/cli/shared_utils.go
@@ -6,27 +6,11 @@ import (
 	"os"
 	"os/exec"
 	"strings"
-	"sync"
 	"time"
 
 	"github.com/githubnext/gh-aw/pkg/console"
 )
 
-// Global cache for current repository info
-var (
-	getCurrentRepoSlugOnce sync.Once
-	currentRepoSlugResult  string
-	currentRepoSlugError   error
-)
-
-// ClearCurrentRepoSlugCache clears the current repository slug cache
-// This is useful for testing or when repository context might have changed
-func ClearCurrentRepoSlugCache() {
-	getCurrentRepoSlugOnce = sync.Once{}
-	currentRepoSlugResult = ""
-	currentRepoSlugError = nil
-}
-
 // PullRequest represents a GitHub Pull Request
 type PullRequest struct {
 	Number    int       `json:"number"`
@@ -175,72 +159,3 @@ func WaitForWorkflowCompletion(repoSlug, runID string, timeoutMinutes int, verbo
 		time.Sleep(10 * time.Second)
 	}
 }
-
-// GetCurrentRepoSlug gets the current repository slug (owner/repo) using gh CLI (uncached)
-// Falls back to git remote parsing if gh CLI is not available
-func GetCurrentRepoSlug() (string, error) {
-	// Try gh CLI first (most reliable)
-	cmd := exec.Command("gh", "repo", "view", "--json", "owner,name", "--jq", ".owner.login + \"/\" + .name")
-	output, err := cmd.Output()
-	if err == nil {
-		repoSlug := strings.TrimSpace(string(output))
-		if repoSlug != "" {
-			// Validate format (should be owner/repo)
-			parts := strings.Split(repoSlug, "/")
-			if len(parts) == 2 && parts[0] != "" && parts[1] != "" {
-				return repoSlug, nil
-			}
-		}
-	}
-
-	// Fallback to git remote parsing if gh CLI is not available or fails
-	cmd = exec.Command("git", "remote", "get-url", "origin")
-	output, err = cmd.Output()
-	if err != nil {
-		return "", fmt.Errorf("failed to get current repository (gh CLI and git remote both failed): %w", err)
-	}
-
-	remoteURL := strings.TrimSpace(string(output))
-
-	// Parse GitHub repository from remote URL
-	// Handle both SSH and HTTPS formats
-	var repoPath string
-
-	// SSH format: git@github.com:owner/repo.git
-	if strings.HasPrefix(remoteURL, "git@github.com:") {
-		repoPath = strings.TrimPrefix(remoteURL, "git@github.com:")
-	} else if strings.Contains(remoteURL, "github.com/") {
-		// HTTPS format: https://github.com/owner/repo.git
-		parts := strings.Split(remoteURL, "github.com/")
-		if len(parts) >= 2 {
-			repoPath = parts[1]
-		}
-	} else {
-		return "", fmt.Errorf("remote URL does not appear to be a GitHub repository: %s", remoteURL)
-	}
-
-	// Remove .git suffix if present
-	repoPath = strings.TrimSuffix(repoPath, ".git")
-
-	// Validate format (should be owner/repo)
-	parts := strings.Split(repoPath, "/")
-	if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
-		return "", fmt.Errorf("invalid repository format: %s", repoPath)
-	}
-
-	return repoPath, nil
-}
-
-// GetCurrentRepoSlugCached gets the current repository slug with caching using sync.Once
-// This is the recommended function to use for repository access across the codebase
-func GetCurrentRepoSlugCached() (string, error) {
-	getCurrentRepoSlugOnce.Do(func() {
-		currentRepoSlugResult, currentRepoSlugError = GetCurrentRepoSlug()
-	})
-
-	if currentRepoSlugError != nil {
-		return "", currentRepoSlugError
-	}
-
-	return currentRepoSlugResult, nil
-}

From ac43adfc730ba96ee0e4159fb2e9038448b8d757 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 26 Oct 2025 10:08:56 +0000
Subject: [PATCH 5/8] Rename functions: GetCurrentRepoSlug (cached default),
 getCurrentRepoSlugUncached (unexported)

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
---
 pkg/cli/repo.go               | 10 +++++-----
 pkg/cli/repo_test.go          | 26 +++++++++++++-------------
 pkg/cli/trial_command.go      |  4 ++--
 pkg/cli/trial_command_test.go | 16 ----------------
 4 files changed, 20 insertions(+), 36 deletions(-)

diff --git a/pkg/cli/repo.go b/pkg/cli/repo.go
index 4aff2033107..0448cb9f470 100644
--- a/pkg/cli/repo.go
+++ b/pkg/cli/repo.go
@@ -22,9 +22,9 @@ func ClearCurrentRepoSlugCache() {
 	currentRepoSlugError = nil
 }
 
-// GetCurrentRepoSlug gets the current repository slug (owner/repo) using gh CLI (uncached)
+// getCurrentRepoSlugUncached gets the current repository slug (owner/repo) using gh CLI (uncached)
 // Falls back to git remote parsing if gh CLI is not available
-func GetCurrentRepoSlug() (string, error) {
+func getCurrentRepoSlugUncached() (string, error) {
 	// Try gh CLI first (most reliable)
 	cmd := exec.Command("gh", "repo", "view", "--json", "owner,name", "--jq", ".owner.login + \"/\" + .name")
 	output, err := cmd.Output()
@@ -77,11 +77,11 @@ func GetCurrentRepoSlug() (string, error) {
 	return repoPath, nil
 }
 
-// GetCurrentRepoSlugCached gets the current repository slug with caching using sync.Once
+// GetCurrentRepoSlug gets the current repository slug with caching using sync.Once
 // This is the recommended function to use for repository access across the codebase
-func GetCurrentRepoSlugCached() (string, error) {
+func GetCurrentRepoSlug() (string, error) {
 	getCurrentRepoSlugOnce.Do(func() {
-		currentRepoSlugResult, currentRepoSlugError = GetCurrentRepoSlug()
+		currentRepoSlugResult, currentRepoSlugError = getCurrentRepoSlugUncached()
 	})
 
 	if currentRepoSlugError != nil {
diff --git a/pkg/cli/repo_test.go b/pkg/cli/repo_test.go
index f1c45127c8a..a203082d0e6 100644
--- a/pkg/cli/repo_test.go
+++ b/pkg/cli/repo_test.go
@@ -4,41 +4,41 @@ import (
 	"testing"
 )
 
-// TestGetCurrentRepoSlugCached tests that the cached version calls the underlying function only once
-func TestGetCurrentRepoSlugCached(t *testing.T) {
+// TestGetCurrentRepoSlug tests that the cached version calls the underlying function only once
+func TestGetCurrentRepoSlug(t *testing.T) {
 	// Clear the cache before testing
 	ClearCurrentRepoSlugCache()
 
 	// Note: In a real-world scenario, we would use dependency injection or interfaces
-	// For this test, we'll just verify that multiple calls to GetCurrentRepoSlugCached
+	// For this test, we'll just verify that multiple calls to GetCurrentRepoSlug
 	// produce the same result (which implies caching is working)
 
 	// First call
-	result1, err1 := GetCurrentRepoSlugCached()
+	result1, err1 := GetCurrentRepoSlug()
 	if err1 != nil {
 		t.Logf("First call error (expected if not in a git repo): %v", err1)
 	}
 
 	// Second call
-	result2, err2 := GetCurrentRepoSlugCached()
+	result2, err2 := GetCurrentRepoSlug()
 	if err2 != nil {
 		t.Logf("Second call error (expected if not in a git repo): %v", err2)
 	}
 
 	// Both calls should return the same result and same error
 	if result1 != result2 {
-		t.Errorf("GetCurrentRepoSlugCached returned different results on multiple calls: %s vs %s", result1, result2)
+		t.Errorf("GetCurrentRepoSlug returned different results on multiple calls: %s vs %s", result1, result2)
 	}
 
 	if (err1 == nil) != (err2 == nil) {
-		t.Errorf("GetCurrentRepoSlugCached returned different error states on multiple calls")
+		t.Errorf("GetCurrentRepoSlug returned different error states on multiple calls")
 	}
 
 	if err1 != nil && err2 != nil && err1.Error() != err2.Error() {
-		t.Errorf("GetCurrentRepoSlugCached returned different errors on multiple calls: %v vs %v", err1, err2)
+		t.Errorf("GetCurrentRepoSlug returned different errors on multiple calls: %v vs %v", err1, err2)
 	}
 
-	t.Logf("GetCurrentRepoSlugCached returned consistent results (result: %s, error: %v)", result1, err1)
+	t.Logf("GetCurrentRepoSlug returned consistent results (result: %s, error: %v)", result1, err1)
 }
 
 // TestClearCurrentRepoSlugCache tests that clearing the cache allows a fresh call
@@ -47,7 +47,7 @@ func TestClearCurrentRepoSlugCache(t *testing.T) {
 	ClearCurrentRepoSlugCache()
 
 	// First call
-	result1, err1 := GetCurrentRepoSlugCached()
+	result1, err1 := GetCurrentRepoSlug()
 	if err1 != nil {
 		t.Logf("First call error (expected if not in a git repo): %v", err1)
 	}
@@ -56,18 +56,18 @@ func TestClearCurrentRepoSlugCache(t *testing.T) {
 	ClearCurrentRepoSlugCache()
 
 	// Second call after clearing cache
-	result2, err2 := GetCurrentRepoSlugCached()
+	result2, err2 := GetCurrentRepoSlug()
 	if err2 != nil {
 		t.Logf("Second call error (expected if not in a git repo): %v", err2)
 	}
 
 	// Results should still be the same (we're in the same repo)
 	if result1 != result2 {
-		t.Errorf("GetCurrentRepoSlugCached returned different results after cache clear: %s vs %s", result1, result2)
+		t.Errorf("GetCurrentRepoSlug returned different results after cache clear: %s vs %s", result1, result2)
 	}
 
 	if (err1 == nil) != (err2 == nil) {
-		t.Errorf("GetCurrentRepoSlugCached returned different error states after cache clear")
+		t.Errorf("GetCurrentRepoSlug returned different error states after cache clear")
 	}
 
 	t.Logf("Cache clear test passed (result: %s, error: %v)", result1, err1)
diff --git a/pkg/cli/trial_command.go b/pkg/cli/trial_command.go
index 7966007f2a5..b1b5ecf5512 100644
--- a/pkg/cli/trial_command.go
+++ b/pkg/cli/trial_command.go
@@ -441,9 +441,9 @@ func RunWorkflowTrials(workflowSpecs []string, logicalRepoSpec string, cloneRepo
 }
 
 // getCurrentRepositoryInfo determines the current repository from the gh CLI (cached)
-// This is a wrapper around GetCurrentRepoSlugCached for backward compatibility
+// This is a wrapper around GetCurrentRepoSlug for backward compatibility
 func getCurrentRepositoryInfo() (string, error) {
-	return GetCurrentRepoSlugCached()
+	return GetCurrentRepoSlug()
 }
 
 // getCurrentGitHubUsername gets the current GitHub username from gh CLI
diff --git a/pkg/cli/trial_command_test.go b/pkg/cli/trial_command_test.go
index c7191632434..b15fbc287d9 100644
--- a/pkg/cli/trial_command_test.go
+++ b/pkg/cli/trial_command_test.go
@@ -7,22 +7,6 @@ import (
 	"testing"
 )
 
-func TestGetCurrentRepoSlug(t *testing.T) {
-	// Note: This test would require a real git repository with a GitHub remote
-	// In a real environment, we would mock the exec.Command calls for testing
-	// For now, we'll just test the function exists and has the right signature
-
-	t.Run("function exists", func(t *testing.T) {
-		// Test that the function exists and returns the expected types
-		_, err := GetCurrentRepoSlug()
-		// We expect an error in test environment since there may not be a git repo
-		// but the important thing is that the function compiles and exists
-		if err != nil {
-			t.Logf("Expected error in test environment: %v", err)
-		}
-	})
-}
-
 // Test the host repo slug processing logic with dot notation
 func TestHostRepoSlugProcessing(t *testing.T) {
 	testCases := []struct {

From bae55f8ac0c738f010d1069dbdaa0084c5f0e43f Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 26 Oct 2025 20:46:09 +0000
Subject: [PATCH 6/8] Fix duplicate SanitizeWorkflowName function after merge
 from main

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
---
 pkg/workflow/workflow_name.go | 21 +--------------------
 1 file changed, 1 insertion(+), 20 deletions(-)

diff --git a/pkg/workflow/workflow_name.go b/pkg/workflow/workflow_name.go
index 50c9c9851b3..5ba183f294e 100644
--- a/pkg/workflow/workflow_name.go
+++ b/pkg/workflow/workflow_name.go
@@ -6,30 +6,11 @@ import (
 )
 
 var (
-	// Regular expressions for workflow name sanitization
+	// Regular expressions for identifier conversion (used in ConvertToIdentifier)
 	identifierNonAlphanumeric = regexp.MustCompile(`[^a-z0-9-]`)
 	identifierMultipleHyphens = regexp.MustCompile(`-+`)
 )
 
-// SanitizeWorkflowName sanitizes a workflow name for use in artifact names and file paths
-// Removes or replaces characters that are invalid in YAML artifact names or filesystem paths
-func SanitizeWorkflowName(name string) string {
-	// Replace colons, slashes, and other problematic characters with hyphens
-	sanitized := strings.ReplaceAll(name, ":", "-")
-	sanitized = strings.ReplaceAll(sanitized, "/", "-")
-	sanitized = strings.ReplaceAll(sanitized, "\\", "-")
-	sanitized = strings.ReplaceAll(sanitized, " ", "-")
-	// Remove any remaining special characters that might cause issues
-	sanitized = strings.Map(func(r rune) rune {
-		// Allow alphanumeric, hyphens, underscores, and periods
-		if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == '-' || r == '_' || r == '.' {
-			return r
-		}
-		return '-'
-	}, sanitized)
-	return sanitized
-}
-
 // ConvertToIdentifier converts a workflow name to a valid identifier format
 // by converting to lowercase and replacing spaces with hyphens
 func ConvertToIdentifier(name string) string {

From 4f81e53a4c670ae36f79e39cf0ccc7c9914f2552 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 26 Oct 2025 21:21:23 +0000
Subject: [PATCH 7/8] Improve ConvertToIdentifier documentation and clarity

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
---
 pkg/workflow/workflow_name.go | 17 ++++++++++++-----
 1 file changed, 12 insertions(+), 5 deletions(-)

diff --git a/pkg/workflow/workflow_name.go b/pkg/workflow/workflow_name.go
index 5ba183f294e..fef1effe680 100644
--- a/pkg/workflow/workflow_name.go
+++ b/pkg/workflow/workflow_name.go
@@ -12,23 +12,30 @@ var (
 )
 
 // ConvertToIdentifier converts a workflow name to a valid identifier format
-// by converting to lowercase and replacing spaces with hyphens
+// suitable for use as a user agent string or similar identifier.
+// It converts to lowercase, replaces spaces and underscores with hyphens,
+// removes non-alphanumeric characters (except hyphens), and consolidates multiple hyphens.
+// Returns "github-agentic-workflow" if the result would be empty.
 func ConvertToIdentifier(name string) string {
 	// Convert to lowercase
 	identifier := strings.ToLower(name)
-	// Replace spaces and other common separators with hyphens
+
+	// Replace spaces and underscores with hyphens
 	identifier = strings.ReplaceAll(identifier, " ", "-")
 	identifier = strings.ReplaceAll(identifier, "_", "-")
+
 	// Remove any characters that aren't alphanumeric or hyphens
 	identifier = identifierNonAlphanumeric.ReplaceAllString(identifier, "")
-	// Remove any double hyphens that might have been created
+
+	// Consolidate multiple hyphens into single hyphen
 	identifier = identifierMultipleHyphens.ReplaceAllString(identifier, "-")
+
 	// Remove leading/trailing hyphens
 	identifier = strings.Trim(identifier, "-")
 
-	// If the result is empty, return a default identifier
+	// Return default if empty after sanitization
 	if identifier == "" {
-		identifier = "github-agentic-workflow"
+		return "github-agentic-workflow"
 	}
 
 	return identifier

From 4e989092c27e4d1cb9f042253df37f3c3a59c10f Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 26 Oct 2025 22:27:27 +0000
Subject: [PATCH 8/8] Rename ConvertToIdentifier to SanitizeIdentifier for
 clarity

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
---
 pkg/workflow/codex_engine.go      | 4 ++--
 pkg/workflow/codex_engine_test.go | 6 +++---
 pkg/workflow/workflow_name.go     | 8 ++++----
 3 files changed, 9 insertions(+), 9 deletions(-)

diff --git a/pkg/workflow/codex_engine.go b/pkg/workflow/codex_engine.go
index ad3f543e921..8e917151edc 100644
--- a/pkg/workflow/codex_engine.go
+++ b/pkg/workflow/codex_engine.go
@@ -479,8 +479,8 @@ func (e *CodexEngine) renderGitHubCodexMCPConfig(yaml *strings.Builder, githubTo
 		if workflowData.EngineConfig != nil && workflowData.EngineConfig.UserAgent != "" {
 			userAgent = workflowData.EngineConfig.UserAgent
 		} else if workflowData.Name != "" {
-			// Fall back to converting workflow name to identifier
-			userAgent = ConvertToIdentifier(workflowData.Name)
+			// Fall back to sanitizing workflow name to identifier
+			userAgent = SanitizeIdentifier(workflowData.Name)
 		}
 	}
 	yaml.WriteString("          user_agent = \"" + userAgent + "\"\n")
diff --git a/pkg/workflow/codex_engine_test.go b/pkg/workflow/codex_engine_test.go
index 59964a4df7a..cab6823953c 100644
--- a/pkg/workflow/codex_engine_test.go
+++ b/pkg/workflow/codex_engine_test.go
@@ -484,7 +484,7 @@ func TestCodexEngineRenderMCPConfigUserAgentFromConfig(t *testing.T) {
 	}
 }
 
-func TestConvertToIdentifier(t *testing.T) {
+func TestSanitizeIdentifier(t *testing.T) {
 	tests := []struct {
 		name     string
 		input    string
@@ -544,9 +544,9 @@ func TestConvertToIdentifier(t *testing.T) {
 
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			result := ConvertToIdentifier(tt.input)
+			result := SanitizeIdentifier(tt.input)
 			if result != tt.expected {
-				t.Errorf("ConvertToIdentifier(%q) = %q, expected %q", tt.input, result, tt.expected)
+				t.Errorf("SanitizeIdentifier(%q) = %q, expected %q", tt.input, result, tt.expected)
 			}
 		})
 	}
diff --git a/pkg/workflow/workflow_name.go b/pkg/workflow/workflow_name.go
index fef1effe680..e7eb926d52a 100644
--- a/pkg/workflow/workflow_name.go
+++ b/pkg/workflow/workflow_name.go
@@ -6,17 +6,17 @@ import (
 )
 
 var (
-	// Regular expressions for identifier conversion (used in ConvertToIdentifier)
+	// Regular expressions for identifier sanitization (used in SanitizeIdentifier)
 	identifierNonAlphanumeric = regexp.MustCompile(`[^a-z0-9-]`)
 	identifierMultipleHyphens = regexp.MustCompile(`-+`)
 )
 
-// ConvertToIdentifier converts a workflow name to a valid identifier format
-// suitable for use as a user agent string or similar identifier.
+// SanitizeIdentifier sanitizes a workflow name to create a safe identifier
+// suitable for use as a user agent string or similar context.
 // It converts to lowercase, replaces spaces and underscores with hyphens,
 // removes non-alphanumeric characters (except hyphens), and consolidates multiple hyphens.
 // Returns "github-agentic-workflow" if the result would be empty.
-func ConvertToIdentifier(name string) string {
+func SanitizeIdentifier(name string) string {
 	// Convert to lowercase
 	identifier := strings.ToLower(name)
 
