{"record":{"id":"5fd71061434ef7c0","repo":"chenhg5/cc-connect","slug":"wps-agentspace-dial-w","errorCode":null,"errorMessage":"wps-agentspace: dial: %w","messagePattern":"wps-agentspace: dial: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"platform/wps-agentspace/wpsagentspace.go","lineNumber":360,"sourceCode":"func (p *Platform) connect(ctx context.Context) error {\n\twsURL := p.getWSURL()\n\tslog.Info(\"wps-agentspace: connecting\", \"url\", wsURL)\n\n\theader := http.Header{\n\t\t\"Cookie\":     []string{fmt.Sprintf(\"wps_sid=%s\", p.wpsSid)},\n\t\t\"User-Agent\": []string{\"OpenClaw/Agentspace\"},\n\t\t\"Origin\":     []string{\"https://agentspace.wps.cn\"},\n\t}\n\n\tslog.Debug(\"wps-agentspace: dialing with headers\", \"cookie_length\", len(p.wpsSid))\n\n\tdialer := websocket.Dialer{\n\t\tHandshakeTimeout: 10 * time.Second,\n\t}\n\n\tconn, _, err := dialer.DialContext(ctx, wsURL, header)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"wps-agentspace: dial: %w\", err)\n\t}\n\n\t// Set ping/pong handlers\n\tconn.SetPingHandler(func(appData string) error {\n\t\tslog.Debug(\"wps-agentspace: received ping\")\n\t\treturn conn.WriteControl(websocket.PongMessage, []byte(appData), time.Now().Add(time.Second))\n\t})\n\n\tconn.SetPongHandler(func(appData string) error {\n\t\tslog.Debug(\"wps-agentspace: received pong\")\n\t\treturn nil\n\t})\n\n\tp.mu.Lock()\n\tp.conn = conn\n\tp.mu.Unlock()\n\n\t// Reset backoff on successful connection","sourceCodeStart":342,"sourceCodeEnd":378,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/platform/wps-agentspace/wpsagentspace.go#L342-L378","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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.","Verify the wsURL in config.toml points to a live endpoint: curl -i the HTTP endpoint or wscat to the same URL.","Check network path: DNS resolution, firewall/proxy rules, and that the WPS Agentspace service is running and reachable from the host.","Confirm TLS validity (cert not expired, correct CA); rely on connectLoop backoff retry for transient outages instead of disabling retry."],"exampleFix":"// before: treating all dial errors the same\nif err := p.connectLoop(ctx); err != nil {\n    slog.Error(\"connect failed\", \"err\", err)\n}\n// after: distinguish handshake rejection from transient network errors\nif err := p.connectLoop(ctx); err != nil {\n    if errors.Is(err, websocket.ErrBadHandshake) {\n        slog.Error(\"wps-agentspace: handshake rejected, check URL/credentials\", \"err\", err)\n    } else {\n        slog.Warn(\"wps-agentspace: transient dial failure, will retry\", \"err\", err)\n    }\n}","handlingStrategy":"retry","validationCode":"u, err := url.Parse(cfg.WSURL)\nif err != nil || (u.Scheme != \"ws\" && u.Scheme != \"wss\") {\n    return fmt.Errorf(\"invalid websocket URL %q\", cfg.WSURL)\n}\nhost := u.Hostname()\nport := u.Port()\nif port == \"\" {\n    port = map[bool]string{true: \"443\", false: \"80\"}[u.Scheme == \"wss\"]\n}\nif conn, err := net.DialTimeout(\"tcp\", net.JoinHostPort(host, port), 5*time.Second); err != nil {\n    return fmt.Errorf(\"endpoint %s unreachable before dialing: %w\", u.Host, err)\n} else {\n    conn.Close()\n}","typeGuard":null,"tryCatchPattern":"err := p.connectLoop(ctx)\nvar hsErr error\nif errors.As(err, &hsErr) && errors.Is(hsErr, websocket.ErrBadHandshake) {\n    slog.Error(\"wps-agentspace: server rejected upgrade; check URL/auth\", \"err\", err)\n} else if err != nil {\n    var ne net.Error\n    if errors.As(err, &ne) && ne.Timeout() {\n        slog.Warn(\"wps-agentspace: handshake timed out; check network\", \"err\", err)\n    }\n}","preventionTips":["Validate the ws/wss endpoint with wscat or curl before deploying the config.","Keep connectLoop exponential-backoff retry enabled for transient network outages.","Monitor certificate expiry for wss:// endpoints.","Verify proxy/firewall rules allow outbound TCP to the endpoint port; configure a proxy Dialer if behind a corporate proxy.","Alert on repeated dial failures rather than a single one to reduce noise from blips."],"tags":["go","websocket","network","connection","timeout"],"backgroundTag":"connection-refused","analyzedSha":"4000b2338aa6e850c99df54f8b0ed6ed7460b401","analyzedAt":"2026-09-06T11:45:09.575Z","contentChangedAt":"2026-09-06T11:45:09.575Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}