chenhg5/cc-connect · error

qq: ws write: %w

Error message

qq: ws write: %w

What it means

Returned by callAPI when writing the API request frame to the OneBot WebSocket connection fails (p.conn.WriteMessage under p.mu). callAPI is the shared request path for Start, Send, SendImage, and resolveGroupName, so any of these surface this error when the connection is dead. Typically indicates a closed or broken connection.

Source

Thrown at platform/qq/qq.go:561

	}
	if params != nil {
		req["params"] = params
	}

	ch := make(chan json.RawMessage, 1)
	p.echoCh.Store(echo, ch)
	defer p.echoCh.Delete(echo)

	data, err := json.Marshal(req)
	if err != nil {
		return nil, err
	}

	p.mu.Lock()
	err = p.conn.WriteMessage(websocket.TextMessage, data)
	p.mu.Unlock()
	if err != nil {
		return nil, fmt.Errorf("qq: ws write: %w", err)
	}

	select {
	case raw := <-ch:
		var resp struct {
			Status  string          `json:"status"`
			RetCode int             `json:"retcode"`
			Data    json.RawMessage `json:"data"`
		}
		if json.Unmarshal(raw, &resp) != nil {
			return nil, fmt.Errorf("qq: invalid API response")
		}
		if resp.RetCode != 0 {
			return nil, fmt.Errorf("qq: API %s failed (retcode=%d)", action, resp.RetCode)
		}
		var result map[string]any
		_ = json.Unmarshal(resp.Data, &result)
		return result, nil

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Reconnect: restart the platform (Start) or implement automatic WS reconnect with backoff.
  2. Check the wrapped error — 'broken pipe'/'use of closed network connection' confirms a dead socket.
  3. Enable/verify OneBot heartbeat (ping/pong) so dead connections are detected early.
  4. Ensure Start completed successfully before issuing API calls; a failed Start leaves conn nil.
  5. Serialize sends through a healthy-connection check; recreate the socket on first write failure.

Example fix

// before
err = p.conn.WriteMessage(websocket.TextMessage, data)

// after
if p.conn == nil {
    return nil, fmt.Errorf("qq: not connected")
}
p.conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
err = p.conn.WriteMessage(websocket.TextMessage, data)
Defensive patterns

Strategy: retry

Validate before calling

func (p *Platform) wsHealthy() bool {
    p.mu.Lock()
    defer p.mu.Unlock()
    if p.conn == nil {
        return false
    }
    return p.conn.WriteControl(websocket.PingMessage, nil, time.Now().Add(3*time.Second)) == nil
}

Try / catch

result, err := p.callAPI("send_group_msg", params)
if err != nil && strings.Contains(err.Error(), "ws write") {
    if rerr := p.reconnect(ctx); rerr != nil {
        return fmt.Errorf("qq: reconnect after ws write failure: %w", rerr)
    }
    result, err = p.callAPI("send_group_msg", params)
}

Prevention

When it happens

Trigger: Any callAPI invocation while the WebSocket is closed/dropped: server restarted, network blip, ping/pong keepalive missed, or connection never established; concurrent sends racing a reconnect can also hit a nil/stale conn.

Common situations: OneBot server restarted or reconnected while the adapter kept the old socket; laptop sleep/network switch; long-lived idle connection silently dropped by a NAT/firewall without the adapter noticing.

Related errors


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