diff --git a/pkg/cli/local_workflow_trial_test.go b/pkg/cli/local_workflow_trial_test.go
index 329b687c0a3..5ec79d17855 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/repo.go b/pkg/cli/repo.go
new file mode 100644
index 00000000000..0448cb9f470
--- /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
+}
+
+// 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 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()
+	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
+}
+
+// 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 GetCurrentRepoSlug() (string, error) {
+	getCurrentRepoSlugOnce.Do(func() {
+		currentRepoSlugResult, currentRepoSlugError = getCurrentRepoSlugUncached()
+	})
+
+	if currentRepoSlugError != nil {
+		return "", currentRepoSlugError
+	}
+
+	return currentRepoSlugResult, nil
+}
diff --git a/pkg/cli/repo_test.go b/pkg/cli/repo_test.go
new file mode 100644
index 00000000000..a203082d0e6
--- /dev/null
+++ b/pkg/cli/repo_test.go
@@ -0,0 +1,96 @@
+package cli
+
+import (
+	"testing"
+)
+
+// 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 GetCurrentRepoSlug
+	// produce the same result (which implies caching is working)
+
+	// First call
+	result1, err1 := GetCurrentRepoSlug()
+	if err1 != nil {
+		t.Logf("First call error (expected if not in a git repo): %v", err1)
+	}
+
+	// Second call
+	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("GetCurrentRepoSlug returned different results on multiple calls: %s vs %s", result1, result2)
+	}
+
+	if (err1 == nil) != (err2 == nil) {
+		t.Errorf("GetCurrentRepoSlug returned different error states on multiple calls")
+	}
+
+	if err1 != nil && err2 != nil && err1.Error() != err2.Error() {
+		t.Errorf("GetCurrentRepoSlug returned different errors on multiple calls: %v vs %v", err1, err2)
+	}
+
+	t.Logf("GetCurrentRepoSlug 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 := GetCurrentRepoSlug()
+	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 := 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("GetCurrentRepoSlug returned different results after cache clear: %s vs %s", result1, result2)
+	}
+
+	if (err1 == nil) != (err2 == nil) {
+		t.Errorf("GetCurrentRepoSlug 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/shared_utils.go b/pkg/cli/shared_utils.go
index 361e70127f5..98036fe986e 100644
--- a/pkg/cli/shared_utils.go
+++ b/pkg/cli/shared_utils.go
@@ -159,25 +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
-func GetCurrentRepoSlug() (string, error) {
-	cmd := exec.Command("gh", "repo", "view", "--json", "owner,name", "--jq", ".owner.login + \"/\" + .name")
-	output, err := cmd.Output()
-	if err != nil {
-		return "", fmt.Errorf("failed to get current repository: %w", err)
-	}
-
-	repoSlug := strings.TrimSpace(string(output))
-	if repoSlug == "" {
-		return "", fmt.Errorf("repository slug is empty")
-	}
-
-	// Validate format (should be owner/repo)
-	parts := strings.Split(repoSlug, "/")
-	if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
-		return "", fmt.Errorf("invalid repository format: %s", repoSlug)
-	}
-
-	return repoSlug, nil
-}
diff --git a/pkg/cli/spec_test.go b/pkg/cli/spec_test.go
index 1c2a54f2723..19269e7246e 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..b1b5ecf5512 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 GetCurrentRepoSlug 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 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 {
diff --git a/pkg/workflow/codex_engine.go b/pkg/workflow/codex_engine.go
index 5010d2635e2..8e917151edc 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
@@ -508,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 74c4721234e..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/copilot_engine.go b/pkg/workflow/copilot_engine.go
index b2e9c3bf2d0..21b2287baaa 100644
--- a/pkg/workflow/copilot_engine.go
+++ b/pkg/workflow/copilot_engine.go
@@ -902,7 +902,7 @@ func generateAWFCleanupStep(scriptPath string) GitHubActionStep {
 
 // generateSquidLogsCollectionStep creates a GitHub Actions step to collect Squid logs from AWF
 func generateSquidLogsCollectionStep(workflowName string) GitHubActionStep {
-	sanitizedName := SanitizeWorkflowName(workflowName)
+	sanitizedName := strings.ToLower(SanitizeWorkflowName(workflowName))
 	squidLogsDir := fmt.Sprintf("/tmp/gh-aw/squid-logs-%s/", sanitizedName)
 
 	stepLines := []string{
@@ -924,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 := 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..e7eb926d52a
--- /dev/null
+++ b/pkg/workflow/workflow_name.go
@@ -0,0 +1,42 @@
+package workflow
+
+import (
+	"regexp"
+	"strings"
+)
+
+var (
+	// Regular expressions for identifier sanitization (used in SanitizeIdentifier)
+	identifierNonAlphanumeric = regexp.MustCompile(`[^a-z0-9-]`)
+	identifierMultipleHyphens = regexp.MustCompile(`-+`)
+)
+
+// 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 SanitizeIdentifier(name string) string {
+	// Convert to lowercase
+	identifier := strings.ToLower(name)
+
+	// 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, "")
+
+	// Consolidate multiple hyphens into single hyphen
+	identifier = identifierMultipleHyphens.ReplaceAllString(identifier, "-")
+
+	// Remove leading/trailing hyphens
+	identifier = strings.Trim(identifier, "-")
+
+	// Return default if empty after sanitization
+	if identifier == "" {
+		return "github-agentic-workflow"
+	}
+
+	return identifier
+}
