{"record":{"id":"092ef5cc0ca27485","repo":"fatedier/frp","slug":"client-control-for-run-id-s-is-not-running","errorCode":null,"errorMessage":"client control for run id [%s] is not running","messagePattern":"client control for run id \\[(.+?)\\] is not running","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"server/control.go","lineNumber":326,"sourceCode":"\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\")\n\t\treturn fmt.Errorf(\"work connection pool is full, discarding\")\n\t}\n}\n\nfunc (cm *ControlManager) Close() error {\n\tcm.mu.Lock()\n\tcm.closed = true\n\tctls := make([]*Control, 0, len(cm.ctlsByRunID))\n\tfor _, entry := range cm.ctlsByRunID {\n\t\tctls = append(ctls, entry.ctl)","sourceCodeStart":308,"sourceCodeEnd":344,"githubUrl":"https://github.com/fatedier/frp/blob/6c8a8d0a97d03b44e9528d30b30c70cb9d61b405/server/control.go#L308-L344","documentation":"RegisterWorkConn holds ctl.lifecycleMu and requires ctl.state == controlStateRunning before queuing a work connection. If the control is still pending activation or already closing/closed, the work conn is refused and ownership stays with the caller.","triggerScenarios":"frpc delivers a NewWorkConn while the control is in controlStatePending (login handshake/activation not finished) or during shutdown after the state moved past running. RegisterWorkConn's state check fails and the error is returned.","commonSituations":"frpc sending work connections before it received LoginResp (protocol violation or eager optimisation); work connections arriving concurrently with control close (server shutdown, heartbeat timeout, proxy errors); races during handoff between control generations.","solutions":["Verify frpc and frps versions match; older/patched clients that fire work connections eagerly can hit the pending window.","Let frpc retry: the next ReqWorkConn issued after the control reaches running will succeed.","If embedding, drop the work conn on this error — retrying against a non-running control is pointless."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// wait for running state before sending work conns (client side: wait for LoginResp)\n// server side embedding:\nselect {\ncase <-ctl.runningCh: // or poll state under lifecycleMu\ncase <-time.After(time.Second):\n    return errors.New(\"control not running yet\")\n}","typeGuard":null,"tryCatchPattern":"if err := cm.RegisterWorkConn(ctl, conn); err != nil { conn.Close() } // never retry a non-running control","preventionTips":["Clients must not send NewWorkConn before receiving LoginResp.","Keep frpc and frps versions aligned to avoid protocol timing changes."],"tags":["go","frp","workconn","lifecycle","state-machine"],"backgroundTag":null,"analyzedSha":"6c8a8d0a97d03b44e9528d30b30c70cb9d61b405","analyzedAt":"2026-08-15T06:53:27.215Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}