{"record":{"id":"1b5e3d653ebbbbbf","repo":"sipeed/picoclaw","slug":"failed-to-start-token-refresh-w","errorCode":null,"errorMessage":"failed to start token refresh: %w","messagePattern":"failed to start token refresh: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/channels/qq/qq.go","lineNumber":126,"sourceCode":"\tlogger.InfoC(\"qq\", \"Starting QQ bot (WebSocket mode)\")\n\n\t// Reinitialize shutdown signal for clean restart.\n\tc.done = make(chan struct{})\n\tc.stopOnce = sync.Once{}\n\n\t// create token source\n\tcredentials := &token.QQBotCredentials{\n\t\tAppID:     c.config.AppID,\n\t\tAppSecret: c.config.AppSecret.String(),\n\t}\n\tc.tokenSource = token.NewQQBotTokenSource(credentials)\n\n\t// create child context\n\tc.ctx, c.cancel = context.WithCancel(ctx)\n\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{","sourceCodeStart":108,"sourceCodeEnd":144,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/channels/qq/qq.go#L108-L144","documentation":"QQChannel.Start wraps the error from token.StartRefreshAccessToken, the goroutine/loop that obtains and periodically refreshes the QQ access token using the configured app_id/app_secret. It fails when the very first token request fails — bad credentials, no network to the QQ auth endpoint, or an already-cancelled context — and the underlying error is preserved via %w for inspection.","triggerScenarios":"Calling Start with an incorrect app_secret (auth endpoint returns invalid_client); no DNS/route to openapi.qq.com or the token endpoint from the host; passing an already-cancelled context so the refresh loop exits immediately; QQ open platform outage during startup.","commonSituations":"Copied app_id correctly but app_secret stale after a console reset; container with no egress or wrong proxy env; clock skew breaking token issuance; restarting the channel after a network partition before connectivity is restored.","solutions":["Print the full chain (fmt.Printf(\"%+v\", err) or errors.As) — the wrapped error distinguishes 401/invalid_client (bad credentials) from timeouts/DNS (network).","Fix credentials in the QQ open platform console if the wrapped error indicates authorization failure, then restart the channel.","Restore outbound connectivity to the QQ API host (firewall, DNS, proxy) if the wrapped error is a network class failure.","Retry Start after a short backoff — QQ open platform occasionally returns transient 5xx on the token endpoint."],"exampleFix":"// before: swallowing the chain\nif err := ch.Start(ctx); err != nil {\n    log.Fatal(\"start failed\")\n}\n\n// after: surface the wrapped cause\nif err := ch.Start(ctx); err != nil {\n    var netErr net.Error\n    if errors.As(err, &netErr) {\n        log.Printf(\"qq: transient network issue, retrying: %v\", err)\n        time.Sleep(5 * time.Second)\n        return ch.Start(ctx)\n    }\n    return err // credential/config problem\n}","handlingStrategy":"try-catch","validationCode":"// pre-flight: can we reach the QQ token endpoint with these credentials?\nif _, err := http.Head(\"https://bots.qq.com\"); err != nil {\n    return fmt.Errorf(\"no egress to QQ API, Start will fail: %w\", err)\n}","typeGuard":null,"tryCatchPattern":"if err := ch.Start(ctx); err != nil {\n    var netErr net.Error\n    switch {\n    case errors.As(err, &netErr):\n        retryAfterBackoff() // transient\n    case strings.Contains(err.Error(), \"failed to start token refresh\"):\n        inspectCredentials() // unwrap with %v to see invalid_client vs timeout\n    }\n}","preventionTips":["Always log the wrapped chain (%+v) — it separates credential errors from network errors","Verify credentials with a manual token curl before enabling the channel","Cancel-and-restart cycles: ensure the ctx passed to Start is not already cancelled"],"tags":["qq","token","auth","network","startup"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}