{"record":{"id":"56982f24f7aa1c8c","repo":"router-for-me/CLIProxyAPI","slug":"auth-provider-refresh-returned-invalid-auth-data","errorCode":null,"errorMessage":"auth provider refresh returned invalid auth data","messagePattern":"auth provider refresh returned invalid auth data","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/pluginhost/auth_provider.go","lineNumber":398,"sourceCode":"\t\tdata.ProxyURL = auth.ProxyURL\n\t}\n\tif len(data.Metadata) == 0 {\n\t\tdata.Metadata = cloneAnyMap(auth.Metadata)\n\t}\n\tif len(data.Attributes) == 0 {\n\t\tdata.Attributes = cloneStringMap(auth.Attributes)\n\t}\n\tif len(data.StorageJSON) == 0 {\n\t\tdata.StorageJSON = storageJSONFromAuth(auth)\n\t}\n\tif pluginResp.NextRefreshAfter.IsZero() {\n\t\tdata.NextRefreshAfter = auth.NextRefreshAfter\n\t} else {\n\t\tdata.NextRefreshAfter = pluginResp.NextRefreshAfter\n\t}\n\tnext := h.AuthDataToCoreAuth(data, \"\", data.FileName)\n\tif next == nil {\n\t\treturn nil, true, fmt.Errorf(\"auth provider refresh returned invalid auth data\")\n\t}\n\tnext.Index = auth.Index\n\tnext.CreatedAt = auth.CreatedAt\n\tnext.UpdatedAt = auth.UpdatedAt\n\treturn next, true, nil\n}\n\nfunc (h *Host) AuthDataToCoreAuth(data pluginapi.AuthData, path, fileName string) *coreauth.Auth {\n\tauthDir := \"\"\n\tif h != nil {\n\t\tauthDir = h.hostConfigSummary().AuthDir\n\t}\n\treturn pluginAuthDataToCoreAuth(data, path, fileName, authDir)\n}\n\ntype pluginTokenStorage struct {\n\tprovider string\n\trawJSON  []byte","sourceCodeStart":380,"sourceCodeEnd":416,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/pluginhost/auth_provider.go#L380-L416","documentation":"After a plugin's RefreshAuth returns new auth data, the host merges it with the existing auth (filling missing fields from the old record) and converts it to a core auth object via AuthDataToCoreAuth. If that conversion yields nil, the merged pluginapi.AuthData is fundamentally invalid (no usable provider/type/value combination) and this error is returned with handled=true.","triggerScenarios":"A plugin's RefreshAuth response that omits required fields: empty provider identifier, unknown auth type, or a value payload the host cannot map to a core auth record. The merge only backfills Attributes, StorageJSON, and NextRefreshAfter — it cannot fix a missing provider or type.","commonSituations":"Plugin returns a refresh response with only NextRefreshAfter set (host backfills StorageJSON but the type/provider fields stay empty); plugin written against an older AuthData schema; plugin intentionally returns an empty response to signal 'nothing changed' instead of using the agreed mechanism.","solutions":["Log or inspect the plugin's AuthRefreshResponse: check that Provider, Type/URL fields, and value payload are all populated.","Fix the plugin so RefreshAuth echoes back the AuthData it received (with updated token values) rather than a partial struct.","If the plugin means 'no refresh needed', return the original storage unchanged or an explicit no-op signal, never an empty AuthData.","Update the pluginhost/plugin pair together so both use the same pluginapi version."],"exampleFix":"// plugin side, before\nreturn pluginapi.AuthRefreshResponse{\n\tNextRefreshAfter: time.Now().Add(time.Hour), // everything else empty -> host cannot build core auth\n}, nil\n\n// after\nout := req.AsAuthData() // echo received data\nout.AccessToken = newToken\nout.NextRefreshAfter = time.Now().Add(time.Hour)\nreturn pluginapi.AuthRefreshResponse{AuthData: out}, nil","handlingStrategy":"validation","validationCode":"// Plugin-side, before returning from RefreshAuth\nfunc validAuthData(d pluginapi.AuthData) bool {\n    return strings.TrimSpace(d.Provider) != \"\" && len(d.StorageJSON) > 0\n}\nif !validAuthData(out) {\n    return pluginapi.AuthRefreshResponse{}, fmt.Errorf(\"refresh produced incomplete auth data\")\n}","typeGuard":"func (d pluginapi.AuthData) IsValid() bool {\n    return strings.TrimSpace(d.Provider) != \"\" && (len(d.StorageJSON) > 0 || len(d.Attributes) > 0)\n}","tryCatchPattern":"next, handled, err := host.CallRefreshAuth(ctx, auth)\nif err != nil && strings.Contains(err.Error(), \"invalid auth data\") {\n    // plugin returned a partial refresh payload; force re-login for this provider\n    return forceReLogin(auth)\n}","preventionTips":["Plugin refresh handlers should echo the received AuthData and only overwrite changed token fields.","Add contract tests asserting every plugin refresh response round-trips through AuthDataToCoreAuth."],"tags":["plugin","auth","token-refresh","validation","pluginhost"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}