chenhg5/cc-connect · critical
qq: ws connect failed
Error message
qq: ws connect failed (%s): %w
What it means
Returned by Platform.Start when the WebSocket dial to the OneBot v11 server (p.wsURL) fails. The QQ platform adapter connects via websocket.DefaultDialer.Dial; any dial-layer failure (TCP, TLS, HTTP handshake, auth rejection) is wrapped with the target URL for debugging. It aborts platform startup, so the QQ platform cannot receive or send messages.
Solutions
- Verify the OneBot server is running and reachable: curl the HTTP endpoint or open the ws URL in a WS client.
- Check the qq section of config.toml: ws_url host, port, and scheme (ws:// vs wss://) must match the OneBot server config.
- If the server requires an access token, confirm p.token matches the OneBot access_token exactly (it is sent as 'Authorization: Bearer <token>').
- Inspect the wrapped inner error (%w) for the root cause: connection refused means server down; 401/403 means token mismatch; timeout means network/firewall.
- If using wss through a reverse proxy, ensure the proxy forwards the Upgrade and Connection headers.
Example fix
// before ws_url = "ws://127.0.0.1:6700" // server actually on 3001 // after ws_url = "ws://127.0.0.1:3001" access_token = "my-onebot-token" // must match OneBot access_token
Defensive patterns
Strategy: retry
Validate before calling
u, err := url.Parse(cfg.QQ.WSURL)
if err != nil || (u.Scheme != "ws" && u.Scheme != "wss") {
return fmt.Errorf("invalid qq ws_url: %q", cfg.QQ.WSURL)
}
conn, err := net.DialTimeout("tcp", u.Host, 5*time.Second)
if err != nil {
return fmt.Errorf("onebot server unreachable at %s: %w", u.Host, err)
}
conn.Close() Try / catch
err := p.Start(ctx)
if err != nil && strings.Contains(err.Error(), "ws connect failed") {
slog.Warn("qq ws dial failed, retrying with backoff", "err", err)
time.Sleep(backoff)
return p.Start(ctx)
} Prevention
- Health-check the OneBot endpoint before starting the platform
- Pin ws scheme and port in config and validate on load
- Keep access_token in sync with the OneBot access_token
- Use wss behind proxies that strip WS upgrade headers only if they support forwarding them
When it happens
Trigger: Calling Start() when the OneBot server is unreachable at p.wsURL (wrong host/port, server down), the URL scheme is wrong (ws vs wss), the reverse-proxy returns non-101, or the Bearer token header is rejected and the handshake is refused.
Common situations: config.toml has the wrong ws_url for the OneBot/NapCat/Lagrange server; the OneBot service is not running or is listening on a different port; running behind a proxy that rejects WebSocket upgrades; expired or incorrect access token; firewall blocking the connection.
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/0158975a1a2201a4.
Report an issue: GitHub.
Appendix: source
Thrown at platform/qq/qq.go:81
allowFrom: allowFrom,
shareSessionInChannel: shareSessionInChannel,
httpURL: httpURL,
}, nil
}
func (p *Platform) Name() string { return "qq" }
func (p *Platform) Start(handler core.MessageHandler) error {
p.handler = handler
header := http.Header{}
if p.token != "" {
header.Set("Authorization", "Bearer "+p.token)
}
conn, _, err := websocket.DefaultDialer.Dial(p.wsURL, header)
if err != nil {
return fmt.Errorf("qq: ws connect failed (%s): %w", p.wsURL, err)
}
p.conn = conn
slog.Info("qq: connected to OneBot", "url", p.wsURL)
ctx, cancel := context.WithCancel(context.Background())
p.cancel = cancel
// Start readLoop BEFORE callAPI: callAPI's response is routed by readLoop,
// so calling it first would always time out after 15s and leave selfID=0,
// which disables the self-message filter in handleMessage and lets the bot
// respond to its own messages.
go p.readLoop(ctx)
// Get bot self info
if info, err := p.callAPI("get_login_info", nil); err == nil {
if uid, ok := info["user_id"].(float64); ok {
p.selfID = int64(uid)View on GitHub (pinned to 4000b2338a)