fatedier/frp · warning
client control for run id [%s] is not running
Error message
client control for run id [%s] is not running
What it means
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.
Source
Thrown at server/control.go:326
entry, ok := cm.lockCurrentRun(ctl.runID, false)
if !ok {
cm.mu.RLock()
closed := cm.closed
cm.mu.RUnlock()
if closed {
return fmt.Errorf("control manager is closed")
}
return fmt.Errorf("client control for run id [%s] is no longer current", ctl.runID)
}
defer entry.runMu.Unlock()
if entry.ctl != ctl || entry.id != ctl.controlID {
return fmt.Errorf("client control for run id [%s] is no longer current", ctl.runID)
}
ctl.lifecycleMu.Lock()
defer ctl.lifecycleMu.Unlock()
if ctl.state != controlStateRunning {
return fmt.Errorf("client control for run id [%s] is not running", ctl.runID)
}
select {
case ctl.workConnCh <- conn:
ctl.xl.Debugf("new work connection registered")
return nil
default:
ctl.xl.Debugf("work connection pool is full, discarding")
return fmt.Errorf("work connection pool is full, discarding")
}
}
func (cm *ControlManager) Close() error {
cm.mu.Lock()
cm.closed = true
ctls := make([]*Control, 0, len(cm.ctlsByRunID))
for _, entry := range cm.ctlsByRunID {
ctls = append(ctls, entry.ctl)View on GitHub (pinned to 6c8a8d0a97)
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.
Defensive patterns
Strategy: validation
Validate before calling
// wait for running state before sending work conns (client side: wait for LoginResp)
// server side embedding:
select {
case <-ctl.runningCh: // or poll state under lifecycleMu
case <-time.After(time.Second):
return errors.New("control not running yet")
} Try / catch
if err := cm.RegisterWorkConn(ctl, conn); err != nil { conn.Close() } // never retry a non-running control Prevention
- Clients must not send NewWorkConn before receiving LoginResp.
- Keep frpc and frps versions aligned to avoid protocol timing changes.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- control manager is closed
- client control for run id [%s] is no longer current
- work connection pool is full, discarding
- control is already closed
- exec configuration is required when type is 'exec'
AI-assisted analysis of fatedier/frp@6c8a8d0a97 (2026-08-15).
Data as JSON: /api/errors/092ef5cc0ca27485.
Report an issue: GitHub.