From 08ffc7e567ff7092a8c0816c26db52d795327299 Mon Sep 17 00:00:00 2001 From: Qinen Zhu Date: Fri, 24 Jul 2026 11:42:46 +0800 Subject: [PATCH] fix: distinguish GitHub Copilot App execution environment Detect the Copilot App-specific AI_AGENT value before Copilot CLI markers so execution.environment reports the correct caller. Add precedence coverage and keep agent test environments isolated. --- cli/azd/cmd/auto_install_integration_test.go | 2 ++ .../runcontext/agentdetect/detect_env.go | 31 +++++++++++++------ .../runcontext/agentdetect/detect_test.go | 18 +++++++++++ .../internal/runcontext/agentdetect/types.go | 4 +++ cli/azd/internal/terminal/terminal_test.go | 1 + cli/azd/internal/tracing/fields/fields.go | 1 + .../tracing/resource/exec_environment.go | 2 ++ docs/reference/telemetry-data.md | 1 + 8 files changed, 50 insertions(+), 10 deletions(-) diff --git a/cli/azd/cmd/auto_install_integration_test.go b/cli/azd/cmd/auto_install_integration_test.go index 4f106582249..add29bc7cde 100644 --- a/cli/azd/cmd/auto_install_integration_test.go +++ b/cli/azd/cmd/auto_install_integration_test.go @@ -301,6 +301,8 @@ func containsNoPromptFalse(args []string) bool { // This ensures tests are isolated from the ambient environment. func clearAgentEnvVarsForTest(t *testing.T) { envVarsToUnset := []string{ + // GitHub Copilot App + "AI_AGENT", // Claude Code "CLAUDE_CODE", "CLAUDE_CODE_ENTRYPOINT", // GitHub Copilot CLI diff --git a/cli/azd/internal/runcontext/agentdetect/detect_env.go b/cli/azd/internal/runcontext/agentdetect/detect_env.go index 6730be37ac6..5b562d9262a 100644 --- a/cli/azd/internal/runcontext/agentdetect/detect_env.go +++ b/cli/azd/internal/runcontext/agentdetect/detect_env.go @@ -12,13 +12,21 @@ import ( // envVarPattern maps environment variables to agent types. type envVarPattern struct { - envVar string - agentType AgentType + envVar string + expectedValue string + agentType AgentType } // knownEnvVarPatterns defines environment variables that indicate known AI agents. // These are checked in order, so more specific patterns should come first. var knownEnvVarPatterns = []envVarPattern{ + // GitHub Copilot App can expose Copilot CLI markers, so check its host-specific marker first. + { + envVar: "AI_AGENT", + expectedValue: "github_copilot_app_agent", + agentType: AgentTypeGitHubCopilotApp, + }, + // Claude Code - Anthropic's coding agent {envVar: "CLAUDE_CODE", agentType: AgentTypeClaudeCode}, {envVar: "CLAUDE_CODE_ENTRYPOINT", agentType: AgentTypeClaudeCode}, @@ -39,14 +47,17 @@ var knownEnvVarPatterns = []envVarPattern{ // detectFromEnvVars checks for known AI agent environment variables. func detectFromEnvVars() AgentInfo { for _, pattern := range knownEnvVarPatterns { - if _, exists := os.LookupEnv(pattern.envVar); exists { - return AgentInfo{ - Type: pattern.agentType, - Name: pattern.agentType.DisplayName(), - Source: DetectionSourceEnvVar, - Detected: true, - Details: pattern.envVar, - } + value, exists := os.LookupEnv(pattern.envVar) + if !exists || (pattern.expectedValue != "" && value != pattern.expectedValue) { + continue + } + + return AgentInfo{ + Type: pattern.agentType, + Name: pattern.agentType.DisplayName(), + Source: DetectionSourceEnvVar, + Detected: true, + Details: pattern.envVar, } } diff --git a/cli/azd/internal/runcontext/agentdetect/detect_test.go b/cli/azd/internal/runcontext/agentdetect/detect_test.go index 3dd5d2b9ff7..27a4648cc0f 100644 --- a/cli/azd/internal/runcontext/agentdetect/detect_test.go +++ b/cli/azd/internal/runcontext/agentdetect/detect_test.go @@ -19,6 +19,7 @@ func TestAgentType_DisplayName(t *testing.T) { }{ {AgentTypeClaudeCode, "Claude Code"}, {AgentTypeGitHubCopilotCLI, "GitHub Copilot CLI"}, + {AgentTypeGitHubCopilotApp, "GitHub Copilot App"}, {AgentTypeVSCodeCopilot, "VS Code GitHub Copilot"}, {AgentTypeGemini, "Gemini"}, {AgentTypeOpenCode, "OpenCode"}, @@ -53,6 +54,21 @@ func TestDetectFromEnvVars(t *testing.T) { expectedAgent: AgentTypeUnknown, detected: false, }, + { + name: "GitHub Copilot App takes precedence over Copilot CLI", + envVars: map[string]string{ + "AI_AGENT": "github_copilot_app_agent", + "COPILOT_CLI": "1", + }, + expectedAgent: AgentTypeGitHubCopilotApp, + detected: true, + }, + { + name: "Unrecognized AI_AGENT value", + envVars: map[string]string{"AI_AGENT": "another_agent"}, + expectedAgent: AgentTypeUnknown, + detected: false, + }, { name: "Claude Code via CLAUDE_CODE", envVars: map[string]string{"CLAUDE_CODE": "1"}, @@ -327,6 +343,8 @@ func TestDisableAgentDetect(t *testing.T) { // This list must be kept in sync with knownEnvVarPatterns in detect_env.go. func clearAgentEnvVars(t *testing.T) { envVarsToUnset := []string{ + // GitHub Copilot App + "AI_AGENT", // Claude Code "CLAUDE_CODE", "CLAUDE_CODE_ENTRYPOINT", // GitHub Copilot CLI diff --git a/cli/azd/internal/runcontext/agentdetect/types.go b/cli/azd/internal/runcontext/agentdetect/types.go index cc972a1a80c..07f6b7ab5ba 100644 --- a/cli/azd/internal/runcontext/agentdetect/types.go +++ b/cli/azd/internal/runcontext/agentdetect/types.go @@ -16,6 +16,8 @@ const ( AgentTypeClaudeCode AgentType = "claude-code" // AgentTypeGitHubCopilotCLI is GitHub's Copilot CLI agent. AgentTypeGitHubCopilotCLI AgentType = "github-copilot-cli" + // AgentTypeGitHubCopilotApp is GitHub's Copilot App agent. + AgentTypeGitHubCopilotApp AgentType = "github-copilot-app" // AgentTypeVSCodeCopilot is VS Code GitHub Copilot extension. AgentTypeVSCodeCopilot AgentType = "vscode-copilot" // AgentTypeGemini is Google's Gemini CLI. @@ -36,6 +38,8 @@ func (a AgentType) DisplayName() string { return "Claude Code" case AgentTypeGitHubCopilotCLI: return "GitHub Copilot CLI" + case AgentTypeGitHubCopilotApp: + return "GitHub Copilot App" case AgentTypeVSCodeCopilot: return "VS Code GitHub Copilot" case AgentTypeGemini: diff --git a/cli/azd/internal/terminal/terminal_test.go b/cli/azd/internal/terminal/terminal_test.go index c83fd2375fe..6c588832fbd 100644 --- a/cli/azd/internal/terminal/terminal_test.go +++ b/cli/azd/internal/terminal/terminal_test.go @@ -92,6 +92,7 @@ func clearTestEnvVars(t *testing.T) { envVarsToUnset := []string{ "AZD_FORCE_TTY", // Agent env vars + "AI_AGENT", "CLAUDE_CODE", "CLAUDE_CODE_ENTRYPOINT", "GITHUB_COPILOT_CLI", "GH_COPILOT", "COPILOT_CLI", "GEMINI_CLI", "GEMINI_CLI_NO_RELAUNCH", diff --git a/cli/azd/internal/tracing/fields/fields.go b/cli/azd/internal/tracing/fields/fields.go index de360f23974..f59a0bf8cd0 100644 --- a/cli/azd/internal/tracing/fields/fields.go +++ b/cli/azd/internal/tracing/fields/fields.go @@ -297,6 +297,7 @@ const ( // AI Coding Agent environments EnvClaudeCode = "Claude Code" EnvGitHubCopilotCLI = "GitHub Copilot CLI" + EnvGitHubCopilotApp = "GitHub Copilot App" EnvGemini = "Gemini" EnvOpenCode = "OpenCode" diff --git a/cli/azd/internal/tracing/resource/exec_environment.go b/cli/azd/internal/tracing/resource/exec_environment.go index eb323344da1..c452942e9ec 100644 --- a/cli/azd/internal/tracing/resource/exec_environment.go +++ b/cli/azd/internal/tracing/resource/exec_environment.go @@ -75,6 +75,8 @@ func execEnvFromAgent() string { return fields.EnvClaudeCode case agentdetect.AgentTypeGitHubCopilotCLI: return fields.EnvGitHubCopilotCLI + case agentdetect.AgentTypeGitHubCopilotApp: + return fields.EnvGitHubCopilotApp case agentdetect.AgentTypeVSCodeCopilot: return fields.EnvVSCodeAzureCopilot case agentdetect.AgentTypeGemini: diff --git a/docs/reference/telemetry-data.md b/docs/reference/telemetry-data.md index 74a8dcebe62..202e8cce7d5 100644 --- a/docs/reference/telemetry-data.md +++ b/docs/reference/telemetry-data.md @@ -582,6 +582,7 @@ The `execution.environment` field identifies where azd is running. Format: `