{"record":{"id":"337d70cd379cb992","repo":"chenhg5/cc-connect","slug":"http-d-s-337d70","errorCode":null,"errorMessage":"HTTP %d: %s","messagePattern":"HTTP (.+?): (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"platform/tuitui/tuitui.go","lineNumber":729,"sourceCode":"\treq, err := http.NewRequestWithContext(ctx, http.MethodPost, u.String(), bytes.NewReader(data))\n\tif err != nil {\n\t\treturn err\n\t}\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\tresp, err := p.client.Do(req)\n\tif err != nil {\n\t\treturn errorsRedacted(err, p.appSecret)\n\t}\n\tdefer func() { _ = resp.Body.Close() }()\n\tbody, err := io.ReadAll(io.LimitReader(resp.Body, maxJSONResponseBytes+1))\n\tif err != nil {\n\t\treturn err\n\t}\n\tif len(body) > maxJSONResponseBytes {\n\t\treturn fmt.Errorf(\"tuitui: %s response exceeds %d bytes\", apiPath, maxJSONResponseBytes)\n\t}\n\tif resp.StatusCode < 200 || resp.StatusCode >= 300 {\n\t\treturn fmt.Errorf(\"HTTP %d: %s\", resp.StatusCode, body)\n\t}\n\tvar apiResp struct {\n\t\tErrCode int    `json:\"errcode\"`\n\t\tErrMsg  string `json:\"errmsg\"`\n\t}\n\tif out != nil {\n\t\tif err := json.Unmarshal(body, out); err != nil {\n\t\t\treturn fmt.Errorf(\"tuitui: decode %s response: %w (body_len=%d)\", apiPath, err, len(body))\n\t\t}\n\t\treturn nil\n\t}\n\tif len(bytes.TrimSpace(body)) == 0 {\n\t\treturn nil\n\t}\n\tif err := json.Unmarshal(body, &apiResp); err != nil {\n\t\treturn fmt.Errorf(\"tuitui: decode %s response: %w (body_len=%d)\", apiPath, err, len(body))\n\t}\n\tif apiResp.ErrCode != 0 {","sourceCodeStart":711,"sourceCodeEnd":747,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/platform/tuitui/tuitui.go#L711-L747","documentation":"postJSON treats any HTTP status outside 200-299 as a failure and includes up to the whole response body in the error. This means the transport succeeded but the Tuitui API (or an intermediary) returned an error status such as 401, 403, 404 or 500.","triggerScenarios":"Expired/invalid app secret (401/403), wrong API path (404), server error (5xx) on any postJSON call: history fetches, getChannelInfo, reactToMessage, sendPayload.","commonSituations":"Rotated Tuitui credentials not updated in config; typo in webhook base URL; Tuitui outage or gateway error page; corporate proxy intercepting requests.","solutions":["Check the HTTP status and body in the error: 401/403 means re-check appSecret/webhook credentials in config.toml.","404: verify the API path/base URL matches the current Tuitui API version.","5xx: retry with backoff; check Tuitui service status.","Confirm network/proxy settings aren't intercepting or rewriting requests."],"exampleFix":"// before\n// HTTP 401: {\"errmsg\":\"invalid app_secret\"} — stale secret in config\n// after\n# config.toml\n[platforms.tuitui]\napp_secret = \"<newly-rotated-secret>\"","handlingStrategy":"retry","validationCode":"if p.appSecret == \"\" || p.webhookBase == \"\" {\n    return errors.New(\"tuitui credentials/base URL not configured\")\n}","typeGuard":null,"tryCatchPattern":"if err := p.postJSON(ctx, path, req, &out); err != nil {\n    var httpErr httpStatusError\n    if errors.As(err, &httpErr) && httpErr.StatusCode >= 500 {\n        return retryWithBackoff(ctx, call)\n    }\n    return err // 4xx: fix credentials/URL, don't retry\n}","preventionTips":["Rotate app secrets in config immediately after upstream rotation","Validate the base URL against current API docs","Distinguish 4xx (fix config) from 5xx (retry) before acting"],"tags":["http","api","tuitui"],"backgroundTag":"http-error-response","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"}