{"record":{"id":"19d0128972a1aef6","repo":"chenhg5/cc-connect","slug":"token-response-decode-w","errorCode":null,"errorMessage":"token response decode: %w","messagePattern":"token response decode: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"platform/qqbot/qqbot.go","lineNumber":590,"sourceCode":"\t})\n\n\tresp, err := core.HTTPClient.Post(tokenURL, \"application/json\", bytes.NewReader(body))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"token request failed: %w\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\traw, _ := io.ReadAll(resp.Body)\n\t\treturn fmt.Errorf(\"token request returned %d: %s\", resp.StatusCode, raw)\n\t}\n\n\tvar result struct {\n\t\tAccessToken string `json:\"access_token\"`\n\t\tExpiresIn   string `json:\"expires_in\"`\n\t}\n\tif err := json.NewDecoder(resp.Body).Decode(&result); err != nil {\n\t\treturn fmt.Errorf(\"token response decode: %w\", err)\n\t}\n\tif result.AccessToken == \"\" {\n\t\treturn fmt.Errorf(\"empty access_token in response\")\n\t}\n\n\tvar expiresSec int\n\t_, _ = fmt.Sscanf(result.ExpiresIn, \"%d\", &expiresSec)\n\tif expiresSec <= 0 {\n\t\texpiresSec = 7200\n\t}\n\n\tp.tokenMu.Lock()\n\tp.token = result.AccessToken\n\tp.tokenExpiry = time.Now().Add(time.Duration(expiresSec) * time.Second)\n\tp.tokenMu.Unlock()\n\n\tslog.Debug(\"qqbot: access token refreshed\", \"expires_in\", expiresSec)\n\treturn nil","sourceCodeStart":572,"sourceCodeEnd":608,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/platform/qqbot/qqbot.go#L572-L608","documentation":"The token endpoint returned HTTP 200 but its body could not be parsed as the expected JSON ({\"access_token\":..., \"expires_in\":...}). refreshToken wraps the json.Decoder error with this message (platform/qqbot/qqbot.go:590). Usually indicates an HTML error page, proxy interstitial, or an API response-format change.","triggerScenarios":"refreshToken receives a 200 response whose body is not valid JSON or not the expected shape: captive portal/proxy HTML page, truncated response, or QQ changing the token endpoint response format.","commonSituations":"Hotel/corporate captive portals intercepting HTTPS (rare with TLS), a transparent proxy returning HTML, or running an old cc-connect version against an updated QQ API.","solutions":["Inspect the wrapped decode error; if it mentions HTML, a proxy/portal is intercepting traffic — bypass or fix the proxy.","Verify the token endpoint URL has not been changed and the deployed version of cc-connect is current.","Test the endpoint directly with curl using the same appId/clientSecret payload.","Update cc-connect if QQ changed the response schema."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// preflight: ensure the token endpoint returns JSON\nresp, err := http.Post(tokenURL, \"application/json\", body)\nif err == nil {\n    var probe map[string]any\n    if json.NewDecoder(resp.Body).Decode(&probe) != nil {\n        log.Println(\"token endpoint not returning JSON — check proxy/portal\")\n    }\n    resp.Body.Close()\n}","typeGuard":null,"tryCatchPattern":"if err := platform.Start(ctx); err != nil {\n    if strings.Contains(err.Error(), \"token response decode\") {\n        slog.Error(\"qqbot token endpoint returned non-JSON (proxy/portal or API change)\", \"err\", err)\n        // retry once, then fall back to manual endpoint check\n    }\n}","preventionTips":["Run on hosts without captive portals or HTML-injecting transparent proxies.","Keep cc-connect updated so response schema changes in the QQ API are picked up.","Monitor for repeated decode errors — they indicate environmental interception, not transient faults.","Pin an egress path (allowlist the QQ API domain) in corporate networks."],"tags":["qqbot","json","oauth","token","invalid-json-response"],"backgroundTag":"json-unmarshal-failed","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"}