Skip to content
This repository was archived by the owner on Sep 18, 2025. It is now read-only.
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Fix GitHub Copilot authentication conflicts by prioritizing GITHUB_CO…
…PILOT_TOKEN

Address the issue where GITHUB_TOKEN conflicts with GitHub CLI tools by:
- Prioritizing GITHUB_COPILOT_TOKEN environment variable over GITHUB_TOKEN
- Updating token loading logic to avoid CLI token conflicts
- Modifying error messages to reference the correct token variable
- Updating documentation to reflect the new token preference

This resolves 401 Unauthorized errors when users have standard GitHub tokens without Copilot scope.

🤖 Generated with opencode
Co-Authored-By: opencode <noreply@opencode.ai>
  • Loading branch information
2 people authored and David Collado Sela committed Jul 7, 2025
commit 5abea9309cf7e6ea0626d06ab1843b53e267b9d9
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ You can configure OpenCode using environment variables:
| `ANTHROPIC_API_KEY` | For Claude models |
| `OPENAI_API_KEY` | For OpenAI models |
| `GEMINI_API_KEY` | For Google Gemini models |
| `GITHUB_TOKEN` | For Github Copilot models (see [Using GitHub Copilot](#using-github-copilot)) |
| `GITHUB_COPILOT_TOKEN` | For Github Copilot models (see [Using GitHub Copilot](#using-github-copilot)) |
| `VERTEXAI_PROJECT` | For Google Cloud VertexAI (Gemini) |
| `VERTEXAI_LOCATION` | For Google Cloud VertexAI (Gemini) |
| `GROQ_API_KEY` | For Groq models |
Expand Down Expand Up @@ -648,7 +648,7 @@ the tool with your github account. This should create a github token at one of t
- ~/.config/github-copilot/[hosts,apps].json
- $XDG_CONFIG_HOME/github-copilot/[hosts,apps].json

If using an explicit github token, you may either set the $GITHUB_TOKEN environment variable or add it to the opencode.json config file at `providers.copilot.apiKey`.
If using an explicit github token, you may either set the $GITHUB_COPILOT_TOKEN environment variable or add it to the opencode.json config file at `providers.copilot.apiKey`.

## Using a self-hosted model provider

Expand Down
36 changes: 19 additions & 17 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -969,18 +969,24 @@ func UpdateTheme(themeName string) error {
})
}

// LoadGitHubToken loads GitHub token from config files, environment variables, or other sources
// LoadGitHubToken loads GitHub Copilot token from config files, environment variables, or other sources
// Returns the token if found, or a special error "no_copilot_token" if no token is found
// This follows the 4-step flow: 1. Check if Copilot is enabled, 2. Check for token in config folder
// This prioritizes GITHUB_COPILOT_TOKEN to avoid conflicts with standard GitHub CLI tools
func LoadGitHubToken() (string, error) {
logging.Debug("Attempting to load GitHub token for Copilot")
logging.Debug("Attempting to load GitHub Copilot token")

// First check environment variables
for _, envName := range []string{"GITHUB_TOKEN", "GITHUB_COPILOT_TOKEN", "GH_COPILOT_TOKEN"} {
if token := os.Getenv(envName); token != "" {
logging.Debug("Found GitHub token in environment variable", "env_var", envName)
return token, nil
}
// 1. Environment variable (prioritize Copilot-specific token)
var token string
if token = os.Getenv("GITHUB_COPILOT_TOKEN"); token != "" {
logging.Debug("Loaded GitHub Copilot API key from GITHUB_COPILOT_TOKEN environment variable")
return token, nil
}

// 2. API key from config options
cfg := Get()
if token = cfg.Providers[models.ProviderCopilot].APIKey; token != "" {
logging.Debug("Loaded GitHub Copilot API key from the '.opencode.json' configuration file")
return token, nil
}

// Get config directory
Expand All @@ -997,14 +1003,12 @@ func LoadGitHubToken() (string, error) {
configDir = filepath.Join(os.Getenv("HOME"), ".config")
}

// Check standard Copilot config files
// 3. Try both hosts.json and apps.json files
filePaths := []string{
filepath.Join(configDir, "github-copilot", "hosts.json"),
filepath.Join(configDir, "github-copilot", "apps.json"),
}

logging.Debug("Checking Copilot config files")

for _, filePath := range filePaths {
data, err := os.ReadFile(filePath)
if err != nil {
Expand All @@ -1016,20 +1020,18 @@ func LoadGitHubToken() (string, error) {
continue
}

// For hosts.json, we expect keys like "github.com"
// For apps.json, we expect keys like "github.com:Iv1.b507a08c87ecfe98"
for key, value := range config {
if strings.Contains(key, "github.com") {
if oauthToken, ok := value["oauth_token"].(string); ok && oauthToken != "" {
logging.Debug("Found GitHub token in config file", "path", filePath)
if oauthToken, ok := value["oauth_token"].(string); ok {
logging.Debug("Loaded GitHub Copilot token from the standard user configuration file")
return oauthToken, nil
}
}
}
}

// Return a special error that indicates we need to use device code flow
logging.Debug("No Copilot token found - will need to use device code flow")
logging.Debug("No GitHub Copilot token found - will need to use device code flow")
return "", fmt.Errorf("no_copilot_token")
}

18 changes: 7 additions & 11 deletions internal/llm/provider/copilot.go
Original file line number Diff line number Diff line change
Expand Up @@ -314,8 +314,7 @@ func saveGitHubToken(token string) {
}
}

// Set environment variables for immediate use
os.Setenv("GITHUB_TOKEN", token)
// Set environment variables for immediate use (only GITHUB_COPILOT_TOKEN)
os.Setenv("GITHUB_COPILOT_TOKEN", token)
}

Expand Down Expand Up @@ -371,12 +370,9 @@ func newCopilotClient(opts providerClientOptions) CopilotClient {
// Try to get GitHub token from multiple sources
var githubToken string

// 1. Check environment variables first (fastest)
for _, envVar := range []string{"GITHUB_TOKEN", "GITHUB_COPILOT_TOKEN", "GH_COPILOT_TOKEN"} {
if token := os.Getenv(envVar); token != "" {
githubToken = token
break
}
// 1. Check GITHUB_COPILOT_TOKEN environment variable first
if token := os.Getenv("GITHUB_COPILOT_TOKEN"); token != "" {
githubToken = token
}

// 2. API key from options
Expand Down Expand Up @@ -430,7 +426,7 @@ func newCopilotClient(opts providerClientOptions) CopilotClient {
}

if githubToken == "" {
logging.Error("GitHub token is required for Copilot provider. Set GITHUB_TOKEN environment variable, configure it in opencode.json, or ensure GitHub CLI/Copilot is properly authenticated.")
logging.Error("GitHub Copilot token is required for Copilot provider. Set GITHUB_COPILOT_TOKEN environment variable, configure it in opencode.json, or ensure GitHub Copilot is properly authenticated.")
return &copilotClient{
providerOptions: opts,
options: copilotOpts,
Expand Down Expand Up @@ -864,8 +860,8 @@ func (c *copilotClient) shouldRetry(attempts int, err error) (bool, int64, error
// Try to refresh the bearer token
var githubToken string

// 1. Environment variable
githubToken = os.Getenv("GITHUB_TOKEN")
// 1. Check GITHUB_COPILOT_TOKEN environment variable
githubToken = os.Getenv("GITHUB_COPILOT_TOKEN")

// 2. API key from options
if githubToken == "" {
Expand Down