fatedier/frp · error

invalid run id: %w

Error message

invalid run id: %w

What it means

Returned by Service.RegisterControl when validation.ValidateRunID rejects the client-supplied Login.RunID (or the server-generated one). ValidateRunID (pkg/config/v1/validation/name.go) requires the run id to be non-empty, at most 64 bytes, valid UTF-8, and contain only printable characters. The run id keys the control connection in ctlManager, so malformed ids are refused at login time.

Source

Thrown at server/service.go:796

		}
	case wire.ProtocolV2:
		if udpPacketCodec != "" && udpPacketCodec != wire.UDPPacketCodecBinary {
			return nil, fmt.Errorf("unsupported UDP packet codec selection: %s", udpPacketCodec)
		}
	default:
		return nil, fmt.Errorf("unsupported wire protocol: %s", wireProtocol)
	}
	// If client's RunID is empty, it's a new client, we just create a new controller.
	// Otherwise, we check if there is one controller has the same run id. If so, we release previous controller and start new one.
	var err error
	if loginMsg.RunID == "" {
		loginMsg.RunID, err = util.RandID()
		if err != nil {
			return nil, err
		}
	}
	if err := validation.ValidateRunID(loginMsg.RunID); err != nil {
		return nil, fmt.Errorf("invalid run id: %w", err)
	}

	ctx := netpkg.NewContextFromConn(ctlConn)
	xl := xlog.FromContextSafe(ctx)
	xl.AppendPrefix(loginMsg.RunID)
	ctx = xlog.NewContext(ctx, xl)
	xl.Infof("client login info: ip [%s] version [%s] hostname [%s] os [%s] arch [%s]",
		ctlConn.RemoteAddr().String(), loginMsg.Version, loginMsg.Hostname, loginMsg.Os, loginMsg.Arch)

	// Check auth.
	authVerifier := svr.auth.Verifier
	if internal && loginMsg.ClientSpec.AlwaysAuthPass {
		authVerifier = auth.AlwaysPassVerifier
	}
	if err := authVerifier.VerifyLogin(loginMsg); err != nil {
		return nil, err
	}

View on GitHub (pinned to 6c8a8d0a97)

Solutions

  1. Let frp generate the run id: send Login.RunID empty so the server assigns util.RandID()
  2. If you set RunID yourself, keep it under 64 bytes, valid UTF-8, printable ASCII-safe (e.g. UUID/hex)
  3. Upgrade both frpc and frps so run id generation follows the same rules
  4. Inspect the login payload on the wire to confirm the run id is not being truncated or corrupted

Example fix

// before
login := &msg.Login{RunID: fmt.Sprintf("%s-%s-%s", host, user, tags)}

// after
login := &msg.Login{RunID: ""} // server assigns a valid random run id
Defensive patterns

Strategy: validation

Validate before calling

import "github.com/fatedier/frp/pkg/config/v1/validation"

// before login
if err := validation.ValidateRunID(runID); err != nil {
    // regenerate or trim the run id instead of sending it
    runID = ""
}
login := &msg.Login{RunID: runID}

Type guard

func isValidRunID(s string) bool {
    return s != "" && len(s) <= 64 && utf8.ValidString(s)
}

Prevention

When it happens

Trigger: A Login message whose RunID exceeds 64 bytes, is empty after generation failure, contains non-UTF-8 bytes, or includes control/non-printable characters. Seen when a custom or patched client injects its own run id, or when a middlebox corrupts the login message.

Common situations: Custom clients generating run ids from hostnames/user input that are too long or contain unusual characters; older frpc forks that set run id from an unvalidated field; fuzzed or corrupted login frames; proxies that alter the JSON/yaml login payload.

Related errors


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