fatedier/frp · error

complete client config error: %v

Error message

complete client config error: %v

What it means

Returned when v1.ClientCommonConfig.Complete() fails after the SSH payload flags were successfully parsed. Complete() fills defaults and validates the assembled client config (server address, user, TLS settings, etc.). The underlying reason is both written to the client's SSH session and wrapped in this error on the server side.

Source

Thrown at pkg/ssh/server.go:115

	s.sshConn = sshConn

	addr, extraPayload, err := s.waitForwardAddrAndExtraPayload(channels, requests, 3*time.Second)
	if err != nil {
		return err
	}

	clientCfg, pc, helpMessage, err := s.parseClientAndProxyConfigurer(addr, extraPayload)
	if err != nil {
		if errors.Is(err, flag.ErrHelp) {
			s.writeToClient(helpMessage)
			return nil
		}
		s.writeToClient(err.Error())
		return fmt.Errorf("parse flags from ssh client error: %v", err)
	}
	if err := clientCfg.Complete(); err != nil {
		s.writeToClient(fmt.Sprintf("failed to complete client config: %v", err))
		return fmt.Errorf("complete client config error: %v", err)
	}
	if sshConn.Permissions != nil {
		clientCfg.User = util.EmptyOr(sshConn.Permissions.Extensions["user"], clientCfg.User)
	}
	pc.Complete()

	vc, err := virtual.NewClient(virtual.ClientOptions{
		Common: clientCfg,
		Spec: &msg.ClientSpec{
			Type: "ssh-tunnel",
			// If ssh does not require authentication, then the virtual client needs to authenticate through a token.
			// Otherwise, once ssh authentication is passed, the virtual client does not need to authenticate again.
			AlwaysAuthPass: !s.sc.NoClientAuth,
		},
		HandleWorkConnCb: func(base *v1.ProxyBaseConfig, workConn net.Conn, m *msg.StartWorkConn) bool {
			// join workConn and ssh channel
			c, err := s.openConn(addr)
			if err != nil {

View on GitHub (pinned to 6c8a8d0a97)

Solutions

  1. Check the message echoed to the SSH session — it prefixes 'failed to complete client config: <reason>'.
  2. Strip common-config flags from the ssh command; the SSH tunnel derives server/user/token from the session itself.
  3. Keep only proxy-level flags (local address/port, remotePort, custom domains) in the SSH payload.
  4. If you need full common config, use the frpc binary with a config file instead of the SSH tunnel path.
  5. Verify frpc-side tooling/frps versions match when driving this programmatically.

Example fix

# before
ssh v0@frps "tcp 127.0.0.1:22 --serverAddr frps.example.com --token xxx"

# after (common config comes from the session; keep proxy flags only)
ssh v0@frps "tcp 127.0.0.1:22 --remotePort 6000"
Defensive patterns

Strategy: validation

Try / catch

ssh v0@host "tcp 127.0.0.1:22 --remotePort 6000" 2>&1 | tee /dev/stderr | grep -q "failed to complete client config" && echo "fix flags: use proxy-level flags only"

Prevention

When it happens

Trigger: Flags parsed but the resulting common config is inconsistent — e.g. server address derived from the connection is unusable, a transport option combination is rejected by Complete(), or an auth field is invalid. The concrete sub-error from Complete() is included after 'complete client config error: '.

Common situations: Passing common-config flags over SSH that conflict with the fixed server-side values (token, user, tls settings); version drift where an old ssh client sends flags that the new frps Complete() rejects; specifying options that only make sense in standalone frpc mode.

Related errors


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