{"record":{"id":"6e216140016e7c55","repo":"router-for-me/CLIProxyAPI","slug":"auth-provider-s-returned-auth-without-provider","errorCode":null,"errorMessage":"auth provider %s returned auth without provider","messagePattern":"auth provider (.+?) returned auth without provider","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/pluginhost/auth_provider.go","lineNumber":240,"sourceCode":"\treq.RawJSON = bytes.Clone(req.RawJSON)\n\tresp, errParse := provider.ParseAuth(ctx, req)\n\tif errParse != nil {\n\t\treturn nil, false, errParse\n\t}\n\tif !resp.Handled {\n\t\treturn nil, false, nil\n\t}\n\tdatas := pluginAuthParseResponseAuths(resp)\n\tauths = make([]*coreauth.Auth, 0, len(datas))\n\tfor _, data := range datas {\n\t\tif strings.TrimSpace(data.Provider) == \"\" {\n\t\t\tdata.Provider = req.Provider\n\t\t}\n\t\tif strings.TrimSpace(data.Provider) == \"\" {\n\t\t\tdata.Provider = normalizeProviderID(provider.Identifier())\n\t\t}\n\t\tif normalizeProviderID(data.Provider) == \"\" {\n\t\t\treturn nil, true, fmt.Errorf(\"auth provider %s returned auth without provider\", record.id)\n\t\t}\n\t\tparsed := h.AuthDataToCoreAuth(data, req.Path, req.FileName)\n\t\tif parsed == nil {\n\t\t\treturn nil, true, fmt.Errorf(\"auth provider %s returned invalid auth data\", record.id)\n\t\t}\n\t\tauths = append(auths, parsed)\n\t}\n\treturn auths, true, nil\n}\n\nfunc pluginAuthParseResponseAuths(resp pluginapi.AuthParseResponse) []pluginapi.AuthData {\n\tif len(resp.Auths) > 0 {\n\t\treturn append([]pluginapi.AuthData(nil), resp.Auths...)\n\t}\n\treturn []pluginapi.AuthData{resp.Auth}\n}\n\nfunc (h *Host) StartLogin(ctx context.Context, provider string, baseURL string) (pluginapi.AuthLoginStartResponse, bool, error) {","sourceCodeStart":222,"sourceCodeEnd":258,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/pluginhost/auth_provider.go#L222-L258","documentation":"Thrown by Host.callParseAuths when a plugin's ParseAuth response contains an auth entry whose provider field cannot be normalized to a non-empty identifier, even after defaulting to the request provider and the plugin's own declared identifier. Every auth must be attributable to a provider, so the whole response is rejected.","triggerScenarios":"Plugin returns AuthData with empty/whitespace Provider while req.Provider and provider.Identifier() also normalize to empty (e.g. plugin identifier unset or non-normalizable); plugin returns auths array entries with provider omitted.","commonSituations":"Plugin author forgets to set Provider on returned AuthData and the plugin lacks a usable identifier; identifier format changed in a plugin version so normalizeProviderID yields empty.","solutions":["In the plugin, always set Provider on each returned AuthData (or give the plugin a valid identifier)","Pass a non-empty req.Provider when invoking parse so the default applies","Upgrade the plugin to a version compatible with the current identifier normalization rules"],"exampleFix":"// before\nresp.Auth = pluginapi.AuthData{} // provider empty\n\n// after\nresp.Auth = pluginapi.AuthData{Provider: \"gemini\"}","handlingStrategy":"validation","validationCode":"// plugin side, before returning:\nfor i := range resp.Auths {\n    if strings.TrimSpace(resp.Auths[i].Provider) == \"\" {\n        resp.Auths[i].Provider = myProviderID\n    }\n}","typeGuard":null,"tryCatchPattern":"if err != nil && strings.Contains(err.Error(), \"returned auth without provider\") {\n    // plugin contract violation: fix the plugin's ParseAuth response\n}","preventionTips":["Always populate Provider on returned AuthData in plugin implementations","Set a valid plugin identifier so the host can default it"],"tags":["go","pluginhost","auth","plugin","contract","validation"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}