{"record":{"id":"a592cad04141eff7","repo":"sipeed/picoclaw","slug":"failed-to-get-websocket-info-w","errorCode":null,"errorMessage":"failed to get websocket info: %w","messagePattern":"failed to get websocket info: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/channels/qq/qq.go","lineNumber":141,"sourceCode":"\n\t// start auto-refresh token goroutine\n\tif err := token.StartRefreshAccessToken(c.ctx, c.tokenSource); err != nil {\n\t\treturn fmt.Errorf(\"failed to start token refresh: %w\", err)\n\t}\n\n\t// initialize OpenAPI client\n\tc.api = botgo.NewOpenAPI(c.config.AppID, c.tokenSource).WithTimeout(5 * time.Second)\n\n\t// register event handlers\n\tintent := event.RegisterHandlers(\n\t\tc.handleC2CMessage(),\n\t\tc.handleGroupATMessage(),\n\t)\n\n\t// get WebSocket endpoint\n\twsInfo, err := c.api.WS(c.ctx, nil, \"\")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to get websocket info: %w\", err)\n\t}\n\n\tlogger.InfoCF(\"qq\", \"Got WebSocket info\", map[string]any{\n\t\t\"shards\": wsInfo.Shards,\n\t})\n\n\t// create and save sessionManager\n\tc.sessionManager = botgo.NewSessionManager()\n\n\t// start WebSocket connection in goroutine to avoid blocking\n\tgo func() {\n\t\tif err := c.sessionManager.Start(wsInfo, c.tokenSource, &intent); err != nil {\n\t\t\tlogger.ErrorCF(\"qq\", \"WebSocket session error\", map[string]any{\n\t\t\t\t\"error\": err.Error(),\n\t\t\t})\n\t\t\tc.SetRunning(false)\n\t\t}\n\t}()","sourceCodeStart":123,"sourceCodeEnd":159,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/channels/qq/qq.go#L123-L159","documentation":"QQChannel.Start calls c.api.WS(c.ctx, nil, \"\") on the botgo OpenAPI client to fetch the WebSocket gateway URL for the bot's session manager. When that HTTP call fails — auth rejected, network error, or QQ API 5xx — the error is wrapped as \"failed to get websocket info\". Until this succeeds the channel has no gateway and cannot receive events.","triggerScenarios":"Start() invoked with an invalid/expired access token (token source returned a token the WS endpoint rejects); network failure reaching the QQ open platform API; QQ API incident returning 5xx; sandbox environments without egress to the QQ domain.","commonSituations":"app_secret rotated so freshly minted tokens are for the wrong credentials; corporate proxies blocking WebSocket upgrade discovery; QQ open platform maintenance windows; intermittent DNS failures at startup causing repeated Start/Stop cycles.","solutions":["Inspect the wrapped error (errors.Unwrap / %v chain) to tell auth failure (fix credentials) from network failure (fix connectivity).","Retry Start with backoff — the WS info call is a plain HTTP GET and commonly succeeds on the second attempt after a transient failure.","Verify the bot application is published/active in the QQ console (unactivated apps can be denied gateway access).","Check egress rules allow https and wss to the QQ open platform endpoints from the host/container."],"exampleFix":"// before: single-shot start\nif err := qqCh.Start(ctx); err != nil { return err }\n\n// after: retry transient gateway-lookup failures with backoff\nvar err error\nfor attempt := 0; attempt < 3; attempt++ {\n    if err = qqCh.Start(ctx); err == nil { break }\n    if strings.Contains(err.Error(), \"websocket info\") { // transient path\n        time.Sleep(time.Duration(attempt+1) * 2 * time.Second)\n        continue\n    }\n    break\n}","handlingStrategy":"retry","validationCode":"// cheap connectivity gate before Start\nif _, err := net.DialTimeout(\"tcp\", \"api.sgroup.qq.com:443\", 3*time.Second); err != nil {\n    return fmt.Errorf(\"QQ API unreachable, deferring channel start: %w\", err)\n}","typeGuard":null,"tryCatchPattern":"// transient gateway lookups: bounded retry with backoff\nvar err error\nfor i := 0; i < 3; i++ {\n    if err = ch.Start(ctx); err == nil || !strings.Contains(err.Error(), \"websocket info\") { break }\n    select { case <-ctx.Done(): return ctx.Err(); case <-time.After(time.Duration(i+1) * 2 * time.Second): }\n}","preventionTips":["Treat WS-info failure as transient first, credential error second (check the wrapped cause)","Keep egress to QQ openapi hosts allowed in firewall/NetworkPolicy","Monitor Start failures and alert on repeated restarts"],"tags":["qq","websocket","network","startup","retry"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}