{"record":{"id":"744eeb602e686d60","repo":"charmbracelet/crush","slug":"github-copilot-not-available","errorCode":null,"errorMessage":"github copilot not available","messagePattern":"github copilot not available","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"info","filePath":"internal/oauth/copilot/oauth.go","lineNumber":25,"sourceCode":"\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/charmbracelet/crush/internal/oauth\"\n)\n\nconst (\n\tclientID = \"Iv1.b507a08c87ecfe98\"\n\n\tdeviceCodeURL   = \"https://github.com/login/device/code\"\n\taccessTokenURL  = \"https://github.com/login/oauth/access_token\"\n\tcopilotTokenURL = \"https://api.github.com/copilot_internal/v2/token\"\n)\n\nvar ErrNotAvailable = errors.New(\"github copilot not available\")\n\ntype DeviceCode struct {\n\tDeviceCode      string `json:\"device_code\"`\n\tUserCode        string `json:\"user_code\"`\n\tVerificationURI string `json:\"verification_uri\"`\n\tExpiresIn       int    `json:\"expires_in\"`\n\tInterval        int    `json:\"interval\"`\n}\n\n// RequestDeviceCode initiates the device code flow with GitHub.\nfunc RequestDeviceCode(ctx context.Context) (*DeviceCode, error) {\n\tdata := url.Values{}\n\tdata.Set(\"client_id\", clientID)\n\tdata.Set(\"scope\", \"read:user\")\n\n\treq, err := http.NewRequestWithContext(ctx, \"POST\", deviceCodeURL, strings.NewReader(data.Encode()))\n\tif err != nil {\n\t\treturn nil, err","sourceCodeStart":7,"sourceCodeEnd":43,"githubUrl":"https://github.com/charmbracelet/crush/blob/7944b8e52225d8805e31eacbf7ef24856b0dfb7a/internal/oauth/copilot/oauth.go#L7-L43","documentation":"ErrNotAvailable signals that GitHub Copilot OAuth is not available in this build or context. loginCopilot and getCopilotToken return it as a sentinel error instead of attempting the OAuth device flow. Callers should compare with errors.Is and skip or fall back to another provider.","triggerScenarios":"Calling loginCopilot or getCopilotToken when the Copilot provider is disabled/unavailable (e.g. no Copilot subscription or the feature is stubbed out in the build).","commonSituations":"Users select GitHub Copilot as a provider in crush but the Copilot integration is unavailable; scripts automating provider login hit this sentinel and must fall back to another auth path.","solutions":["Check for the sentinel with errors.Is(err, copilot.ErrNotAvailable) and treat it as 'provider unavailable', not a hard failure.","Fall back to a different provider or prompt the user to pick another auth method.","Verify the build/context actually supports Copilot OAuth before calling loginCopilot."],"exampleFix":"// before\ntoken, err := copilot.GetToken(ctx)\nif err != nil { return err }\n// after\ntoken, err := copilot.GetToken(ctx)\nif errors.Is(err, copilot.ErrNotAvailable) {\n    return ErrCopilotUnsupported // degrade gracefully\n}\nif err != nil { return err }","handlingStrategy":"fallback","validationCode":"if copilotErr := tryCopilotLogin(); errors.Is(copilotErr, copilot.ErrNotAvailable) { /* provider disabled */ }","typeGuard":"func isCopilotUnavailable(err error) bool { return errors.Is(err, copilot.ErrNotAvailable) }","tryCatchPattern":"token, err := loginCopilot(ctx)\nif errors.Is(err, copilot.ErrNotAvailable) {\n    return fallbackProviderLogin(ctx)\n}\nif err != nil { return err }","preventionTips":["Always compare against the sentinel with errors.Is, not string matching.","Have a non-Copilot provider configured as a fallback.","Check provider availability before offering Copilot in the UI."],"tags":["oauth","github-copilot","sentinel-error"],"backgroundTag":"provider-unavailable","analyzedSha":"7944b8e52225d8805e31eacbf7ef24856b0dfb7a","analyzedAt":"2026-08-29T12:48:59.079Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}