chenhg5/cc-connect · error

expected READY event, got op=%d t=%s

Error message

expected READY event, got op=%d t=%s

What it means

Returned when a dispatch frame arrives but is not the expected READY event (op 0 with t=READY). The server sent a different dispatch — commonly RESUMED, an error event, or a heartbeart-adjacent op — so the session ID cannot be captured.

Source

Thrown at platform/qqbot/qqbot.go:770

			"shard":   [2]int{0, 1},
		},
	}
	p.wsMu.Lock()
	err := conn.WriteJSON(identify)
	p.wsMu.Unlock()
	return err
}

func (p *Platform) waitForReady(conn *websocket.Conn) error {
	_ = conn.SetReadDeadline(time.Now().Add(15 * time.Second))
	defer func() { _ = conn.SetReadDeadline(time.Time{}) }()

	var msg wsPayload
	if err := conn.ReadJSON(&msg); err != nil {
		return fmt.Errorf("waiting for ready: %w", err)
	}
	if msg.Op != opDispatch || msg.T != "READY" {
		return fmt.Errorf("expected READY event, got op=%d t=%s", msg.Op, msg.T)
	}

	var ready struct {
		SessionID string `json:"session_id"`
	}
	if err := json.Unmarshal(msg.D, &ready); err != nil {
		slog.Warn("qqbot: failed to parse READY payload", "error", err)
	}
	p.sessionID = ready.SessionID
	if msg.S != nil {
		p.lastSeq.Store(*msg.S)
	}

	slog.Info("qqbot: gateway READY", "session_id", p.sessionID)
	return nil
}

func (p *Platform) heartbeatLoop(ctx context.Context) {

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Log msg.Op and msg.T of the unexpected frame for diagnosis
  2. Clear any persisted session_id and perform a fresh Identify instead of resume
  3. Validate the intents and token in the identify payload
  4. Update event-name expectations if QQ renamed READY/RESUMED semantics

Example fix

// before
if msg.Op != opDispatch || msg.T != "READY" {
    return fmt.Errorf("expected READY event, got op=%d t=%s", msg.Op, msg.T)
}
// after
if msg.Op != opDispatch || msg.T != "READY" {
    return fmt.Errorf("expected READY event, got op=%d t=%s data=%s", msg.Op, msg.T, msg.D)
}
Defensive patterns

Strategy: fallback

Type guard

func isReadyDispatch(msg wsPayload) bool { return msg.Op == opDispatch && msg.T == "READY" }

Try / catch

if err := p.waitForReady(conn); err != nil {
    if strings.Contains(err.Error(), "got op=") {
        slog.Warn("non-READY dispatch, forcing fresh identify", "err", err)
        return p.identifyFresh() // drop stale session_id
    }
    return err
}

Prevention

When it happens

Trigger: After sending Identify, the first dispatch is not READY: server sends RESUMED, a dispatch for a stale session, or an error event because the identify payload was rejected.

Common situations: Stale session_id being resumed unexpectedly; intents mismatch so QQ rejects identify via a dispatch error; protocol changes in event names.

Related errors


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