{"record":{"id":"0dd5597c221557d9","repo":"fatedier/frp","slug":"loginrespmsg-error","errorCode":null,"errorMessage":"loginRespMsg.Error","messagePattern":"loginRespMsg\\.Error","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"client/control_session.go","lineNumber":83,"sourceCode":"\t}\n\tdefer func() {\n\t\tif !success {\n\t\t\t_ = conn.Close()\n\t\t}\n\t}()\n\n\tloginMsg, err := d.buildLoginMsg(previousRunID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tloginResult, err := d.exchangeLogin(conn, loginMsg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tloginRespMsg := loginResult.resp\n\tif loginRespMsg.Error != \"\" {\n\t\treturn nil, errors.New(loginRespMsg.Error)\n\t}\n\n\tvar controlRW io.ReadWriter = conn\n\tif d.clientSpec == nil || d.clientSpec.Type != \"ssh-tunnel\" {\n\t\tcontrolRW, err = d.newControlReadWriter(conn, loginResult.crypto)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"create control crypto read writer: %w\", err)\n\t\t}\n\t}\n\n\tsuccess = true\n\treturn &SessionContext{\n\t\tCommon:         d.common,\n\t\tRunID:          loginRespMsg.RunID,\n\t\tConn:           msg.NewConn(conn, msg.NewReadWriter(controlRW, d.common.Transport.WireProtocol)),\n\t\tAuth:           d.auth,\n\t\tConnector:      newMessageConnector(connector, d.common.Transport.WireProtocol),\n\t\tVnetController: d.vnetController,","sourceCodeStart":65,"sourceCodeEnd":101,"githubUrl":"https://github.com/fatedier/frp/blob/6c8a8d0a97d03b44e9528d30b30c70cb9d61b405/client/control_session.go#L65-L101","documentation":"During frpc→frps login, the server's LoginResp message carries an Error string for rejections; control_session.go turns any non-empty value into the dial error. This is frps actively refusing the client after parsing its Login message — the error text comes straight from the server (e.g. token mismatch), unlike network-level dial failures which error earlier in exchangeLogin.","triggerScenarios":"Login with auth.token not matching frps's token; server running a different version with incompatible login semantics; server at max connections or refusing the user; TLS/multiplexing settings that make frps reject the session at the application layer.","commonSituations":"auth.token typo'd or rotated on one side only after a restart; pointing frpc at an frps that requires authentication while the client config has none; mixed old/new frp versions during an upgrade window.","solutions":["Make auth.token byte-identical in frpc.toml and frps.toml (watch for trailing whitespace)","Read the exact server message — it usually states the precise reason (token, version, limit)","Confirm both sides are compatible frp versions","Check frps logs for the login attempt to see the server-side view"],"exampleFix":"# before (frpc.toml)\nauth:\n  token = \"s3cret! \"   # trailing space -> server rejects\n\n# after\nauth:\n  token = \"s3cret!\"","handlingStrategy":"try-catch","validationCode":"// Before dialing, compare configured token presence with expectations\nif len(svr.cfg.Auth.Token) == 0 && svr.cfg.Auth.Method != \"\" {\n    log.Warn(\"empty auth.token — many frps setups reject tokenless logins\")\n}","typeGuard":null,"tryCatchPattern":"sess, err := dialer.Dial(ctx)\nif err != nil {\n    // LoginResp.Error surfaces here verbatim; retrying is pointless until config matches\n    log.Errorf(\"frps rejected login: %v (check auth.token/version)\", err)\n    return err\n}","preventionTips":["Manage auth.token via a single shared secret source for frps and all frpcs","Don't auto-retry login failures — they are deterministic rejections, not transient errors","Log the server's message; it distinguishes token vs version vs policy rejections"],"tags":["go","frpc","login","authentication","network"],"backgroundTag":null,"analyzedSha":"6c8a8d0a97d03b44e9528d30b30c70cb9d61b405","analyzedAt":"2026-08-15T06:53:27.215Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}