{"record":{"id":"c47d4e3f10cbcdcd","repo":"chenhg5/cc-connect","slug":"waiting-for-hello-w","errorCode":null,"errorMessage":"waiting for hello: %w","messagePattern":"waiting for hello: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"platform/qqbot/qqbot.go","lineNumber":723,"sourceCode":"\t\treturn \"\", fmt.Errorf(\"empty gateway URL in response\")\n\t}\n\treturn result.URL, nil\n}\n\ntype wsPayload struct {\n\tOp int             `json:\"op\"`\n\tD  json.RawMessage `json:\"d,omitempty\"`\n\tS  *int64          `json:\"s,omitempty\"`\n\tT  string          `json:\"t,omitempty\"`\n}\n\nfunc (p *Platform) waitForHello(conn *websocket.Conn) error {\n\t_ = conn.SetReadDeadline(time.Now().Add(15 * time.Second))\n\tdefer func() { _ = conn.SetReadDeadline(time.Time{}) }()\n\n\tvar msg wsPayload\n\tif err := conn.ReadJSON(&msg); err != nil {\n\t\treturn fmt.Errorf(\"waiting for hello: %w\", err)\n\t}\n\tif msg.Op != opHello {\n\t\treturn fmt.Errorf(\"expected op 10 (Hello), got op %d\", msg.Op)\n\t}\n\n\tvar hello struct {\n\t\tHeartbeatInterval int `json:\"heartbeat_interval\"`\n\t}\n\tif err := json.Unmarshal(msg.D, &hello); err != nil {\n\t\tslog.Warn(\"qqbot: failed to parse Hello payload\", \"error\", err)\n\t}\n\tif hello.HeartbeatInterval > 0 {\n\t\tp.heartbeatMs = hello.HeartbeatInterval\n\t} else {\n\t\tp.heartbeatMs = 41250 // sane default\n\t}\n\tp.heartbeatOK.Store(true)\n","sourceCodeStart":705,"sourceCodeEnd":741,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/platform/qqbot/qqbot.go#L705-L741","documentation":"Returned by waitForHello when reading the first WebSocket frame fails before the Hello (op 10) handshake completes. The connection was established but closed or errored within the 15s deadline — network drop, server-side rejection, or premature close.","triggerScenarios":"conn.ReadJSON in waitForHello errors: server closes the connection immediately after dial (bad auth at WS layer), read deadline of 15s expires, or network interruption.","commonSituations":"QQ rejects the connection right after dial because the app is offline/banned; slow network exceeding the 15s deadline; LB/proxy killing idle WS connections.","solutions":["Retry the whole connect flow with backoff — this is often transient","Check the QQ bot app status in the open-platform console (sandbox vs production, banned state)","Increase the 15s hello deadline on slow networks","Ensure only one instance of the bot is connected with the same credentials (QQ may drop duplicates)"],"exampleFix":"// before\n_ = conn.SetReadDeadline(time.Now().Add(15 * time.Second))\n// after\n_ = conn.SetReadDeadline(time.Now().Add(30 * time.Second)) // slower networks","handlingStrategy":"retry","validationCode":"// pre-check: ensure no duplicate bot instance holds the session\n// and credentials were validated via a REST call before dialing","typeGuard":null,"tryCatchPattern":"if err := p.waitForHello(conn); err != nil {\n    var netErr net.Error\n    if errors.As(err, &netErr) && netErr.Timeout() {\n        return retryConnect() // transient, backoff\n    }\n    return err\n}","preventionTips":["Run a single bot instance per appid","Use generous hello/read deadlines on slow networks","Reconnect with exponential backoff and jitter"],"tags":["websocket","handshake","timeout","qqbot"],"backgroundTag":"request-timeout","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"}