Skip to content

Commit 83c37f1

Browse files
Claudelpcox
andcommitted
Completing task
Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>
1 parent 12083e4 commit 83c37f1

6 files changed

Lines changed: 440 additions & 75 deletions

File tree

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
#!/bin/bash
2+
set -e
3+
4+
# copilot_preflight_diagnostic.sh - Pre-flight diagnostic for Copilot engine on GHES
5+
#
6+
# This script performs diagnostic checks before executing Copilot CLI to provide
7+
# clear error messages when Copilot is not properly configured on GHES instances.
8+
#
9+
# Checks performed:
10+
# 1. Token exchange test - Validates COPILOT_GITHUB_TOKEN can exchange for Copilot access
11+
# 2. GHES detection - Identifies GHES environments and validates configuration
12+
# 3. API target validation - Ensures engine.api-target matches GITHUB_API_URL on GHES
13+
#
14+
# Exit codes:
15+
# 0 - All checks passed, safe to proceed
16+
# 1 - Critical failure, should fail the workflow
17+
18+
# Check if we're on GHES (non-GitHub.com environment)
19+
IS_GHES=false
20+
if [ "$GITHUB_SERVER_URL" != "https://github.com" ]; then
21+
IS_GHES=true
22+
echo "🔍 Detected GitHub Enterprise Server environment"
23+
echo " Server URL: $GITHUB_SERVER_URL"
24+
echo " API URL: $GITHUB_API_URL"
25+
fi
26+
27+
# Test 1: Token exchange to Copilot inference API
28+
echo ""
29+
echo "🔍 Testing Copilot token exchange..."
30+
31+
# Construct the token exchange endpoint
32+
TOKEN_EXCHANGE_URL="${GITHUB_API_URL}/copilot_internal/v2/token"
33+
34+
# Attempt token exchange using COPILOT_GITHUB_TOKEN
35+
HTTP_STATUS=$(curl -s -o /tmp/copilot_token_exchange.json -w "%{http_code}" \
36+
-H "Authorization: Bearer ${COPILOT_GITHUB_TOKEN}" \
37+
-H "Accept: application/json" \
38+
"$TOKEN_EXCHANGE_URL" 2>&1 || echo "000")
39+
40+
if [ "$HTTP_STATUS" = "200" ]; then
41+
echo "✅ Token exchange successful (HTTP 200)"
42+
echo " Copilot is licensed and accessible"
43+
elif [ "$HTTP_STATUS" = "403" ]; then
44+
# Parse error message from response
45+
ERROR_MSG=$(cat /tmp/copilot_token_exchange.json 2>/dev/null | grep -o '"message":"[^"]*"' | cut -d'"' -f4 || echo "")
46+
47+
echo "❌ Token exchange failed (HTTP 403)"
48+
echo ""
49+
50+
# Check for specific error messages
51+
if echo "$ERROR_MSG" | grep -qi "not licensed"; then
52+
{
53+
echo "## ❌ Copilot Not Licensed"
54+
echo ""
55+
echo "The token exchange endpoint returned HTTP 403 with message:"
56+
echo "\`\`\`"
57+
echo "$ERROR_MSG"
58+
echo "\`\`\`"
59+
echo ""
60+
echo "**This means Copilot is not licensed for this user/organization on GHES.**"
61+
echo ""
62+
echo "### How to fix:"
63+
echo "1. Ask your GHES administrator to enable Copilot at the **enterprise level**"
64+
echo "2. Ensure a Copilot seat is assigned to your user account"
65+
echo "3. Verify your organization has Copilot enabled"
66+
echo ""
67+
echo "### GHES Admin Steps:"
68+
echo "- Navigate to Enterprise settings → Copilot"
69+
echo "- Enable Copilot for the enterprise"
70+
echo "- Assign licenses to organizations"
71+
echo "- Ensure users have seats assigned"
72+
echo ""
73+
echo "**Note:** This is a licensing issue, not a configuration problem with gh-aw."
74+
} >> "$GITHUB_STEP_SUMMARY"
75+
76+
echo "Copilot is not licensed for this user/org on GHES." >&2
77+
echo "Ask your GHES admin to enable Copilot at the enterprise level and assign a seat." >&2
78+
exit 1
79+
80+
elif echo "$ERROR_MSG" | grep -qi "not accessible by personal access token\|token type"; then
81+
{
82+
echo "## ❌ Incorrect Token Type"
83+
echo ""
84+
echo "The token exchange endpoint returned HTTP 403 with message:"
85+
echo "\`\`\`"
86+
echo "$ERROR_MSG"
87+
echo "\`\`\`"
88+
echo ""
89+
echo "**The token type is not supported for Copilot access.**"
90+
echo ""
91+
echo "### How to fix:"
92+
echo "- Ensure you're using a **fine-grained Personal Access Token** (starts with \`github_pat_\`)"
93+
echo "- Configure the token with **Copilot Requests: Read-only** permission"
94+
echo "- Do NOT use classic PATs (\`ghp_\`) or OAuth tokens (\`gho_\`)"
95+
echo ""
96+
echo "Create a fine-grained PAT at: https://${GITHUB_SERVER_URL#https://}/settings/personal-access-tokens/new"
97+
} >> "$GITHUB_STEP_SUMMARY"
98+
99+
echo "Token type is not supported for Copilot." >&2
100+
echo "Use a fine-grained PAT with Copilot Requests permission." >&2
101+
exit 1
102+
103+
else
104+
# Generic 403 error
105+
{
106+
echo "## ❌ Copilot Access Denied"
107+
echo ""
108+
echo "The token exchange endpoint returned HTTP 403:"
109+
echo "\`\`\`"
110+
echo "$ERROR_MSG"
111+
echo "\`\`\`"
112+
echo ""
113+
echo "**Common causes:**"
114+
echo "- Copilot not licensed for this user/organization"
115+
echo "- Incorrect token permissions"
116+
echo "- Token type not supported"
117+
echo ""
118+
echo "Contact your GHES administrator for assistance."
119+
} >> "$GITHUB_STEP_SUMMARY"
120+
121+
echo "Token exchange failed with HTTP 403: $ERROR_MSG" >&2
122+
exit 1
123+
fi
124+
125+
elif [ "$HTTP_STATUS" = "401" ]; then
126+
{
127+
echo "## ❌ Invalid or Expired Token"
128+
echo ""
129+
echo "The token exchange endpoint returned HTTP 401 (Unauthorized)."
130+
echo ""
131+
echo "**This means COPILOT_GITHUB_TOKEN is invalid or expired.**"
132+
echo ""
133+
echo "### How to fix:"
134+
echo "1. Verify the secret is correctly configured in repository settings"
135+
echo "2. Check if the token has expired (fine-grained PATs have expiration dates)"
136+
echo "3. Regenerate the token if needed"
137+
echo "4. Ensure the token has **Copilot Requests: Read-only** permission"
138+
} >> "$GITHUB_STEP_SUMMARY"
139+
140+
echo "COPILOT_GITHUB_TOKEN is invalid or expired (HTTP 401)" >&2
141+
exit 1
142+
143+
elif [ "$HTTP_STATUS" = "404" ]; then
144+
{
145+
echo "## ❌ Copilot Endpoint Not Found"
146+
echo ""
147+
echo "The token exchange endpoint returned HTTP 404 (Not Found)."
148+
echo ""
149+
echo "**This may indicate:**"
150+
echo "- GHES version does not support Copilot"
151+
echo "- Copilot infrastructure is not enabled on this instance"
152+
echo ""
153+
echo "### How to fix:"
154+
echo "- Verify GHES version supports GitHub Copilot"
155+
echo "- Ask your GHES admin to enable Copilot infrastructure"
156+
echo "- Check endpoint URL: \`$TOKEN_EXCHANGE_URL\`"
157+
} >> "$GITHUB_STEP_SUMMARY"
158+
159+
echo "Copilot endpoint not found (HTTP 404) - GHES may not support Copilot" >&2
160+
exit 1
161+
162+
elif [ "$HTTP_STATUS" = "000" ] || [ -z "$HTTP_STATUS" ]; then
163+
echo "⚠️ Could not connect to token exchange endpoint"
164+
echo " This may indicate network issues or firewall blocking"
165+
echo " Proceeding with Copilot execution (will fail if endpoint is truly unavailable)"
166+
# Don't exit - let Copilot CLI fail with its own error if needed
167+
168+
else
169+
echo "⚠️ Unexpected response from token exchange endpoint (HTTP $HTTP_STATUS)"
170+
echo " Proceeding with Copilot execution"
171+
# Don't exit - unexpected statuses should not block execution
172+
fi
173+
174+
# Test 2: GHES-specific validation
175+
if [ "$IS_GHES" = true ]; then
176+
echo ""
177+
echo "🔍 Running GHES-specific checks..."
178+
179+
# Check if engine.api-target is set (should match GITHUB_API_URL)
180+
# This env var would be set by the compiler if engine.api-target is configured
181+
if [ -n "$COPILOT_API_TARGET" ]; then
182+
if [ "$COPILOT_API_TARGET" != "$GITHUB_API_URL" ]; then
183+
echo "⚠️ Warning: engine.api-target ($COPILOT_API_TARGET) does not match GITHUB_API_URL ($GITHUB_API_URL)"
184+
echo " This may cause API routing issues"
185+
else
186+
echo "✅ engine.api-target matches GITHUB_API_URL"
187+
fi
188+
else
189+
echo "ℹ️ engine.api-target not configured (using default GITHUB_API_URL)"
190+
fi
191+
192+
# Verify GHES API domain is accessible
193+
GHES_DOMAIN=$(echo "$GITHUB_API_URL" | sed -E 's|https?://([^/]+).*|\1|')
194+
if [ -n "$GHES_DOMAIN" ]; then
195+
echo "ℹ️ GHES API domain: $GHES_DOMAIN"
196+
echo " Ensure this domain is in network.allowed if using firewall"
197+
fi
198+
fi
199+
200+
echo ""
201+
echo "✅ Pre-flight diagnostic completed"
202+
echo " Proceeding with Copilot CLI execution..."
203+
204+
# Clean up temporary files
205+
rm -f /tmp/copilot_token_exchange.json
206+
207+
exit 0

pkg/workflow/copilot_engine_execution.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,14 @@ func (e *CopilotEngine) GetExecutionSteps(workflowData *WorkflowData, logFile st
4040

4141
var steps []GitHubActionStep
4242

43+
// Add pre-flight diagnostic step before Copilot CLI execution
44+
// This helps diagnose licensing and configuration issues on GHES
45+
preflightStep := generateCopilotPreflightDiagnosticStep(workflowData)
46+
if len(preflightStep) > 0 {
47+
steps = append(steps, preflightStep)
48+
copilotExecLog.Print("Added pre-flight diagnostic step")
49+
}
50+
4351
// Build copilot CLI arguments based on configuration
4452
var copilotArgs []string
4553
sandboxEnabled := isFirewallEnabled(workflowData)
@@ -446,3 +454,51 @@ func generateCopilotSessionFileCopyStep() GitHubActionStep {
446454

447455
return GitHubActionStep(step)
448456
}
457+
458+
// generateCopilotPreflightDiagnosticStep generates a pre-flight diagnostic step that runs
459+
// before Copilot CLI execution to detect and report licensing/configuration issues early.
460+
// This is especially helpful on GHES where errors are often opaque.
461+
//
462+
// The diagnostic checks:
463+
// 1. Token exchange to Copilot inference API (validates licensing and token validity)
464+
// 2. GHES-specific configuration validation (api-target, network domains)
465+
//
466+
// The step is skipped when:
467+
// - copilot-requests feature is enabled (uses GitHub Actions token, no separate token needed)
468+
// - custom command is specified (non-standard Copilot setup)
469+
func generateCopilotPreflightDiagnosticStep(workflowData *WorkflowData) GitHubActionStep {
470+
// Skip if copilot-requests feature is enabled (uses GitHub Actions token)
471+
if isFeatureEnabled(constants.CopilotRequestsFeatureFlag, workflowData) {
472+
copilotExecLog.Print("Skipping pre-flight diagnostic: copilot-requests feature enabled")
473+
return GitHubActionStep{}
474+
}
475+
476+
// Skip if custom command is specified (non-standard setup)
477+
if workflowData.EngineConfig != nil && workflowData.EngineConfig.Command != "" {
478+
copilotExecLog.Print("Skipping pre-flight diagnostic: custom command specified")
479+
return GitHubActionStep{}
480+
}
481+
482+
copilotExecLog.Print("Generating Copilot pre-flight diagnostic step")
483+
484+
var step []string
485+
step = append(step, " - name: Copilot pre-flight diagnostic")
486+
step = append(step, " id: copilot-preflight")
487+
step = append(step, " continue-on-error: true")
488+
step = append(step, " env:")
489+
490+
// Use COPILOT_GITHUB_TOKEN for the diagnostic
491+
// #nosec G101 -- This is a GitHub Actions expression template, not a hardcoded credential
492+
step = append(step, " COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}")
493+
step = append(step, " GITHUB_SERVER_URL: ${{ github.server_url }}")
494+
step = append(step, " GITHUB_API_URL: ${{ github.api_url }}")
495+
496+
// Pass engine.api-target if configured
497+
if workflowData.EngineConfig != nil && workflowData.EngineConfig.APITarget != "" {
498+
step = append(step, " COPILOT_API_TARGET: "+workflowData.EngineConfig.APITarget)
499+
}
500+
501+
step = append(step, " run: bash /opt/gh-aw/actions/copilot_preflight_diagnostic.sh")
502+
503+
return GitHubActionStep(step)
504+
}

0 commit comments

Comments
 (0)