Skip to content

Fix GitHub Copilot authentication flow #294

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: main
Choose a base branch
from

Conversation

bitomule
Copy link

@bitomule bitomule commented Jul 4, 2025

Summary

This PR fixes the GitHub Copilot authentication flow in OpenCode, which currently has issues with token retrieval and exchange.

Problem Description

Currently, the GitHub Copilot integration has several issues:

  1. The code tries to use standard GitHub tokens (like those from gh auth token) which don't work with Copilot API because they lack the required Copilot scope
  2. There's no proper authentication flow when no token is found
  3. The token exchange process fails due to missing required headers and incorrect API version
  4. Missing documentation on how to properly set up and use the Copilot integration

Solution

This PR implements a complete authentication flow inspired by the one used in the Neovim Copilot plugin:

  1. Properly checks for Copilot tokens in standard locations (hosts.json and apps.json)
  2. Implements OAuth device code flow when no token is found
  3. Adds proper GitHub API headers including the correct API version (X-GitHub-Api-Version: 2022-11-28)
  4. Uses the standard Copilot client ID (same as used by VS Code and Neovim plugins)
  5. Stores tokens in the standard location for reuse across sessions

Documentation

Added comprehensive documentation to the README explaining:

  • How to configure Copilot in OpenCode
  • The authentication flow process
  • Where tokens are stored

This PR makes Copilot integration work seamlessly with OpenCode, especially important for users wanting to access Claude models through Copilot.

David Collado Sela added 7 commits July 4, 2025 15:34
Implement a simplified 4-step authentication flow:
1. Check if Copilot is enabled in config
2. Check for token in config folder
3. If no token, trigger login flow
4. With token ready, open OpenCode normally

Key improvements:
- Cleaner code structure with helper functions
- Better error handling for auth scenarios
- Support for both hosts.json and apps.json formats
- Proper API version headers for GitHub Copilot
- Clear documentation of client ID usage
- Remove redundant checks for Copilot provider in Validate()
- Improve comments for better clarity
- Maintain the same functionality with cleaner code
- Simplify logging in GitHub Copilot authentication
- Remove redundant logging messages
- Remove unused getMapKeys helper function
- Keep only essential user feedback during authentication
- Implement device flow authentication for GitHub Copilot
- Add token saving to standard ~/.config/github-copilot/hosts.json
- Fix token validation and error handling
- Continue polling when receiving empty responses
- Set environment variables for immediate use of token
- Clean and simplify code implementation
- Remove redundant token saving functions
- Combine hosts.json handling in a single function
- Simplify environment variable checks
- Remove excessive debug logging
- Streamline error handling and code paths
}

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To avoid conflicts with other tools like the GitHub CLI (gh) that uses GITHUB_TOKEN, I think we should use only GITHUB_COPILOT_TOKEN for all Copilot-related requests using opencode and leave GITHUB_TOKEN untouched.

logging.Debug("Attempting to load GitHub token for Copilot")

// First check environment variables
for _, envName := range []string{"GITHUB_TOKEN", "GITHUB_COPILOT_TOKEN", "GH_COPILOT_TOKEN"} {
Copy link

@mendesbarreto mendesbarreto Jul 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome, I was just about to create a PR for this myself. We should move away from GITHUB_TOKEN for the reason mentioned here. All the other tools like Gemini and ChatGPT use their own environment variables, so it makes sense to give Copilot its own too. Using GITHUB_TOKEN can be misleading as copilot request don't accept them.

For me this method should be something like this:

func LoadGitHubToken() (string, error) {
	// 1. Environment variable
	var token string
	if token = os.Getenv("GITHUB_COPILOT_TOKEN"); token != "" {
		logging.Debug("Loaded GitHub Copilot API key from a system environment variable.")
		return token, nil
	}

	// 2. API key from options
	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
	var configDir string
	if xdgConfig := os.Getenv("XDG_CONFIG_HOME"); xdgConfig != "" {
		configDir = xdgConfig
	} else if runtime.GOOS == "windows" {
		if localAppData := os.Getenv("LOCALAPPDATA"); localAppData != "" {
			configDir = localAppData
		} else {
			configDir = filepath.Join(os.Getenv("HOME"), "AppData", "Local")
		}
	} else {
		configDir = filepath.Join(os.Getenv("HOME"), ".config")
	}

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

	for _, filePath := range filePaths {
		data, err := os.ReadFile(filePath)
		if err != nil {
			continue
		}

		var config map[string]map[string]interface{}
		if err := json.Unmarshal(data, &config); err != nil {
			continue
		}

		for key, value := range config {
			if strings.Contains(key, "github.com") {
				if oauthToken, ok := value["oauth_token"].(string); ok {
					logging.Debug("Loaded GitHub Copilot token from the standard user configuration file.")
					return oauthToken, nil
				}
			}
		}
	}

	return "", fmt.Errorf("GitHub token not found in standard locations")
}

@mendesbarreto
Copy link

mendesbarreto commented Jul 5, 2025

I tested your PR but ran into this error:

image

This happens because my GITHUB_TOKEN is being rejected by Copilot. This creates a frustrating workflow for anyone who relies on that token for other tools. We shouldn't have to unset an important variable just to open opencode.

So Like I said before we need to move away from GITHUB_TOKEN and use the token from $XDG_CONFIG_HOME/opencode or $XDG_CONFIG_HOME/github-copilot.

If we do this we can fix #294

Thank you so much for the effort =)

…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>
@bitomule
Copy link
Author

bitomule commented Jul 7, 2025

Hey @mendesbarreto pushed a new commit with your feedback. Is this what you had in mind? Thank you for your comments!

@mendesbarreto
Copy link

mendesbarreto commented Jul 8, 2025

It's working =)

image
image

fixes #270

@bitomule
Copy link
Author

bitomule commented Jul 9, 2025

What's the next step to get it merged?

@mendesbarreto
Copy link

mendesbarreto commented Jul 10, 2025

What's the next step to get it merged?

I have been following the tale of Copilot since its conception, and the last one who approved something similar was @kujtimiihoxha. 😊

@mendesbarreto
Copy link

@bitomule I think the why is here:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants