{"record":{"id":"128aae7ae56a131a","repo":"larksuite/cli","slug":"failed-to-parse-tat-response-http-d-w","errorCode":null,"errorMessage":"failed to parse TAT response (HTTP %d): %w","messagePattern":"failed to parse TAT response \\(HTTP (.+?)\\): %w","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/credential/tat_fetch.go","lineNumber":105,"sourceCode":"\t\tif rateLimitErr == nil {\n\t\t\trateLimitErr = errs.NewAPIError(errs.SubtypeRateLimit, \"TAT endpoint rate limited (HTTP 429)\").\n\t\t\t\tWithCode(http.StatusTooManyRequests).\n\t\t\t\tWithRetryable()\n\t\t}\n\t\tif retryAfter := tatRetryAfterSeconds(resp.Header); retryAfter > 0 {\n\t\t\trateLimitErr.RetryAfterSeconds = retryAfter\n\t\t\trateLimitErr.Hint = fmt.Sprintf(\"wait at least %d seconds before retrying; if throttling continues, use exponential backoff with jitter\", retryAfter)\n\t\t} else {\n\t\t\trateLimitErr.Hint = \"use exponential backoff with jitter when retrying\"\n\t\t}\n\t\treturn \"\", rateLimitErr\n\t}\n\n\tvar result tatResponse\n\tif err := json.Unmarshal(body, &result); err != nil {\n\t\t// An unparseable body is ambiguous (covers non-JSON error pages and\n\t\t// truncated payloads); stay untyped so probe callers treat it as noise.\n\t\treturn \"\", fmt.Errorf(\"failed to parse TAT response (HTTP %d): %w\", resp.StatusCode, err)\n\t}\n\n\tif result.Code == 0 && result.AccessToken != \"\" {\n\t\treturn result.AccessToken, nil\n\t}\n\n\t// Transient/server-side failures stay untyped so probe callers stay silent and\n\t// retryers can back off; only deterministic client rejections are typed. Covers\n\t// 5xx and the OAuth transient error strings (server_error,\n\t// temporarily_unavailable, slow_down). HTTP 429 was already returned above\n\t// as a typed rate-limit error with retry guidance and an upstream delay when available.\n\tif resp.StatusCode >= 500 ||\n\t\tresult.Error == \"server_error\" || result.Error == \"temporarily_unavailable\" ||\n\t\tresult.Error == \"slow_down\" {\n\t\treturn \"\", fmt.Errorf(\"TAT endpoint transient failure (HTTP %d, code=%d, error=%q): %s\",\n\t\t\tresp.StatusCode, result.Code, result.Error, result.ErrorDescription)\n\t}\n","sourceCodeStart":87,"sourceCodeEnd":123,"githubUrl":"https://github.com/larksuite/cli/blob/7fd6ef3c07182257ce776cdc5a614e122d5bd4b3/internal/credential/tat_fetch.go#L87-L123","documentation":"FetchTAT fetched a Tenant Access Token response but json.Unmarshal failed on the body, so it cannot parse the JSON envelope into tatResponse. The message embeds the HTTP status of the underlying response and the JSON error. Per the source comment, the body is deliberately left untyped so probe callers treat it as ambiguous noise.","triggerScenarios":"The TAT endpoint returned a body that is not valid JSON or is truncated: e.g. an HTML error page from a proxy/gateway, an empty body, or a connection cut mid-response, regardless of HTTP status.","commonSituations":"Corporate proxies or WAFs intercepting the token endpoint and returning HTML login/block pages; Lark gateway 502/503 pages in HTML; network interruption truncating the response; pointing the client at a wrong base URL that serves a redirect page.","solutions":["Check network path to the TAT endpoint: print/capture the raw body and HTTP status to see whether a proxy, WAF, or gateway page is being returned instead of JSON.","Verify the configured base URL / endpoint host for the token endpoint is the correct Lark/Feishu API domain.","Retry the request; if truncated payloads recur, check for TLS/proxy issues or reduced timeouts cutting responses short.","If a proxy is required, ensure HTTP(S)_PROXY settings are correctly configured for the process."],"exampleFix":"// before (caller sees only the wrapped error)\ntoken, err := credential.FetchTAT(ctx, cfg)\n\n// after (surface the raw body for diagnosis when parsing fails)\ntoken, err := credential.FetchTAT(ctx, cfg)\nif err != nil {\n    if strings.Contains(err.Error(), \"failed to parse TAT response\") {\n        log.Printf(\"TAT parse failure, check proxy/gateway for non-JSON response (status embedded in error): %v\", err)\n    }\n}","handlingStrategy":"retry","validationCode":"// Optionally pre-flight the endpoint shape:\nresp, err := http.Post(tokenURL, \"application/json\", body)\nif err == nil && resp.StatusCode == 200 {\n    var probe struct{ AccessToken string `json:\"access_token\"` }\n    raw, _ := io.ReadAll(io.LimitReader(resp.Body, 1<<16))\n    if json.Unmarshal(raw, &probe) != nil || !strings.HasPrefix(strings.TrimSpace(string(raw)), \"{\") {\n        log.Printf(\"endpoint returning non-JSON body: %.200s\", raw)\n    }\n}","typeGuard":null,"tryCatchPattern":"token, err := credential.FetchTAT(ctx, cfg)\nif err != nil {\n    if strings.Contains(err.Error(), \"failed to parse TAT response\") {\n        // ambiguous: likely proxy/gateway noise — retry after short delay\n        time.Sleep(time.Second)\n        token, err = credential.FetchTAT(ctx, cfg)\n    }\n    if err != nil { return fmt.Errorf(\"obtain TAT: %w\", err) }\n}","preventionTips":["Ensure proxies/WAFs do not intercept the Lark API domain with HTML block pages.","Set generous HTTP timeouts so responses are not truncated mid-body.","Validate the configured base URL points at the official Lark/Feishu API host.","Alert on repeated parse failures — they usually indicate a persistent network-path problem, not a code bug."],"tags":["json","http","token-fetch","proxy"],"backgroundTag":"invalid-json-response","analyzedSha":"7fd6ef3c07182257ce776cdc5a614e122d5bd4b3","analyzedAt":"2026-09-04T21:17:44.649Z","contentChangedAt":"2026-09-04T21:17:44.649Z","schemaVersion":2},"datasetVersion":"2026-09-12T02:17:10.037Z"}