chenhg5/cc-connect · error

wps-agentspace: dial: %w

Error message

wps-agentspace: dial: %w

What it means

connect dials the platform WebSocket with a 10-second handshake timeout and wraps any dialer failure as "wps-agentspace: dial: %w". This covers DNS failure, TCP connect refusal/reset, TLS handshake errors, and HTTP-level rejection of the upgrade (non-101 status), and is retried by connectLoop.

Source

Thrown at platform/wps-agentspace/wpsagentspace.go:360

func (p *Platform) connect(ctx context.Context) error {
	wsURL := p.getWSURL()
	slog.Info("wps-agentspace: connecting", "url", wsURL)

	header := http.Header{
		"Cookie":     []string{fmt.Sprintf("wps_sid=%s", p.wpsSid)},
		"User-Agent": []string{"OpenClaw/Agentspace"},
		"Origin":     []string{"https://agentspace.wps.cn"},
	}

	slog.Debug("wps-agentspace: dialing with headers", "cookie_length", len(p.wpsSid))

	dialer := websocket.Dialer{
		HandshakeTimeout: 10 * time.Second,
	}

	conn, _, err := dialer.DialContext(ctx, wsURL, header)
	if err != nil {
		return fmt.Errorf("wps-agentspace: dial: %w", err)
	}

	// Set ping/pong handlers
	conn.SetPingHandler(func(appData string) error {
		slog.Debug("wps-agentspace: received ping")
		return conn.WriteControl(websocket.PongMessage, []byte(appData), time.Now().Add(time.Second))
	})

	conn.SetPongHandler(func(appData string) error {
		slog.Debug("wps-agentspace: received pong")
		return nil
	})

	p.mu.Lock()
	p.conn = conn
	p.mu.Unlock()

	// Reset backoff on successful connection

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Unwrap and inspect: errors.Is(err, websocket.ErrBadHandshake) plus resp.StatusCode for HTTP-level rejections; net.Error Timeout for the 10s handshake timeout; *net.OpError for DNS/TCP failures.
  2. Verify the wsURL in config.toml points to a live endpoint: curl -i the HTTP endpoint or wscat to the same URL.
  3. Check network path: DNS resolution, firewall/proxy rules, and that the WPS Agentspace service is running and reachable from the host.
  4. Confirm TLS validity (cert not expired, correct CA); rely on connectLoop backoff retry for transient outages instead of disabling retry.

Example fix

// before: treating all dial errors the same
if err := p.connectLoop(ctx); err != nil {
    slog.Error("connect failed", "err", err)
}
// after: distinguish handshake rejection from transient network errors
if err := p.connectLoop(ctx); err != nil {
    if errors.Is(err, websocket.ErrBadHandshake) {
        slog.Error("wps-agentspace: handshake rejected, check URL/credentials", "err", err)
    } else {
        slog.Warn("wps-agentspace: transient dial failure, will retry", "err", err)
    }
}
Defensive patterns

Strategy: retry

Validate before calling

u, err := url.Parse(cfg.WSURL)
if err != nil || (u.Scheme != "ws" && u.Scheme != "wss") {
    return fmt.Errorf("invalid websocket URL %q", cfg.WSURL)
}
host := u.Hostname()
port := u.Port()
if port == "" {
    port = map[bool]string{true: "443", false: "80"}[u.Scheme == "wss"]
}
if conn, err := net.DialTimeout("tcp", net.JoinHostPort(host, port), 5*time.Second); err != nil {
    return fmt.Errorf("endpoint %s unreachable before dialing: %w", u.Host, err)
} else {
    conn.Close()
}

Try / catch

err := p.connectLoop(ctx)
var hsErr error
if errors.As(err, &hsErr) && errors.Is(hsErr, websocket.ErrBadHandshake) {
    slog.Error("wps-agentspace: server rejected upgrade; check URL/auth", "err", err)
} else if err != nil {
    var ne net.Error
    if errors.As(err, &ne) && ne.Timeout() {
        slog.Warn("wps-agentspace: handshake timed out; check network", "err", err)
    }
}

Prevention

When it happens

Trigger: connectLoop calling connect when: the configured ws/wss URL host is wrong or the service is down; a proxy/firewall blocks the port; DNS cannot resolve the host; TLS certificate invalid or expired; the server rejects the upgrade with 4xx/5xx; the 10s HandshakeTimeout expires on a slow network.

Common situations: Typo'd or stale WebSocket endpoint in config.toml; corporate proxy requiring CONNECT that the Dialer does not use; offline/air-gapped deployments; server restarted or rate-limiting connections; self-signed certificates without proper TLS config.

Understand the failure class

Background: ECONNREFUSED and "connection refused" / "could not connect to server" errors: what they mean and how to fix them — this error's family across 44 libraries.

Related errors


AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06). Data as JSON: /api/errors/5fd71061434ef7c0. Report an issue: GitHub.