{"record":{"id":"c62d64880f9f5e25","repo":"fatedier/frp","slug":"client-control-for-run-id-s-is-no-longer-curren","errorCode":null,"errorMessage":"client control for run id [%s] is no longer current","messagePattern":"client control for run id \\[(.+?)\\] is no longer current","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"server/control.go","lineNumber":316,"sourceCode":"\tdefer ctl.lifecycleMu.Unlock()\n\tif ctl.state != controlStateRunning {\n\t\treturn false, nil\n\t}\n\treturn true, admit(ctl.sessionCtx.LoginMsg.User, ctl.sessionCtx.WireProtocol, ctl.sessionCtx.UDPPacketCodec)\n}\n\n// RegisterWorkConn transfers conn to ctl only if ctl is still the current\n// running generation. On error, ownership remains with the caller.\nfunc (cm *ControlManager) RegisterWorkConn(ctl *Control, conn *proxy.WorkConn) error {\n\tentry, ok := cm.lockCurrentRun(ctl.runID, false)\n\tif !ok {\n\t\tcm.mu.RLock()\n\t\tclosed := cm.closed\n\t\tcm.mu.RUnlock()\n\t\tif closed {\n\t\t\treturn fmt.Errorf(\"control manager is closed\")\n\t\t}\n\t\treturn fmt.Errorf(\"client control for run id [%s] is no longer current\", ctl.runID)\n\t}\n\tdefer entry.runMu.Unlock()\n\tif entry.ctl != ctl || entry.id != ctl.controlID {\n\t\treturn fmt.Errorf(\"client control for run id [%s] is no longer current\", ctl.runID)\n\t}\n\n\tctl.lifecycleMu.Lock()\n\tdefer ctl.lifecycleMu.Unlock()\n\tif ctl.state != controlStateRunning {\n\t\treturn fmt.Errorf(\"client control for run id [%s] is not running\", ctl.runID)\n\t}\n\n\tselect {\n\tcase ctl.workConnCh <- conn:\n\t\tctl.xl.Debugf(\"new work connection registered\")\n\t\treturn nil\n\tdefault:\n\t\tctl.xl.Debugf(\"work connection pool is full, discarding\")","sourceCodeStart":298,"sourceCodeEnd":334,"githubUrl":"https://github.com/fatedier/frp/blob/6c8a8d0a97d03b44e9528d30b30c70cb9d61b405/server/control.go#L298-L334","documentation":"RegisterWorkConn could not lock the current run for ctl.runID because the manager no longer has a matching entry (or the entry's run gate changed) and the manager is not closed — i.e. the control generation this work connection belongs to is gone. On error, conn ownership remains with the caller and the connection is closed there.","triggerScenarios":"A new frpc login for the same run ID replaced the old control generation (Add swapped cm.ctlsByRunID[runID]), so the old generation's work connections no longer have a current entry; or the old control closed (e.g. after heartbeat timeout) and its entry was removed. A late NewWorkConn from the old session hits this path.","commonSituations":"frpc reconnecting with the same run ID (server restarted, connection dropped) while old work connections are still in flight; duplicate clients reusing a run ID; races between a control being replaced and frpc fulfilling an earlier ReqWorkConn.","solutions":["Confirm only one frpc uses that run ID / client identity at a time.","If embedding the server, treat this error as benign: close the work conn and let the new generation request its own work connections.","Check frpc logs for a concurrent reconnect; align reconnect logic so the old session's work connections are torn down before new ones are created."],"exampleFix":null,"handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err := cm.RegisterWorkConn(ctl, conn); err != nil {\n    conn.Close() // caller retains ownership on error\n    if !strings.Contains(err.Error(), \"no longer current\") { log.Warn(err) }\n}","preventionTips":["Never reuse a *Control handle across reconnect generations.","Ensure only one live session per run ID to avoid generation swaps."],"tags":["go","frp","workconn","generation","stale-session"],"backgroundTag":null,"analyzedSha":"6c8a8d0a97d03b44e9528d30b30c70cb9d61b405","analyzedAt":"2026-08-15T06:53:27.215Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}