fatedier/frp · warning

client control for run id [%s] is no longer current

Error message

client control for run id [%s] is no longer current

What it means

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.

Source

Thrown at server/control.go:316

	defer ctl.lifecycleMu.Unlock()
	if ctl.state != controlStateRunning {
		return false, nil
	}
	return true, admit(ctl.sessionCtx.LoginMsg.User, ctl.sessionCtx.WireProtocol, ctl.sessionCtx.UDPPacketCodec)
}

// RegisterWorkConn transfers conn to ctl only if ctl is still the current
// running generation. On error, ownership remains with the caller.
func (cm *ControlManager) RegisterWorkConn(ctl *Control, conn *proxy.WorkConn) error {
	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")

View on GitHub (pinned to 6c8a8d0a97)

Solutions

  1. Confirm only one frpc uses that run ID / client identity at a time.
  2. If embedding the server, treat this error as benign: close the work conn and let the new generation request its own work connections.
  3. 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.
Defensive patterns

Strategy: fallback

Try / catch

if err := cm.RegisterWorkConn(ctl, conn); err != nil {
    conn.Close() // caller retains ownership on error
    if !strings.Contains(err.Error(), "no longer current") { log.Warn(err) }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of fatedier/frp@6c8a8d0a97 (2026-08-15). Data as JSON: /api/errors/c62d64880f9f5e25. Report an issue: GitHub.