larksuite/cli · error
failed to parse TAT response (HTTP %d): %w
Error message
failed to parse TAT response (HTTP %d): %w
What it means
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.
Source
Thrown at internal/credential/tat_fetch.go:105
if rateLimitErr == nil {
rateLimitErr = errs.NewAPIError(errs.SubtypeRateLimit, "TAT endpoint rate limited (HTTP 429)").
WithCode(http.StatusTooManyRequests).
WithRetryable()
}
if retryAfter := tatRetryAfterSeconds(resp.Header); retryAfter > 0 {
rateLimitErr.RetryAfterSeconds = retryAfter
rateLimitErr.Hint = fmt.Sprintf("wait at least %d seconds before retrying; if throttling continues, use exponential backoff with jitter", retryAfter)
} else {
rateLimitErr.Hint = "use exponential backoff with jitter when retrying"
}
return "", rateLimitErr
}
var result tatResponse
if err := json.Unmarshal(body, &result); err != nil {
// An unparseable body is ambiguous (covers non-JSON error pages and
// truncated payloads); stay untyped so probe callers treat it as noise.
return "", fmt.Errorf("failed to parse TAT response (HTTP %d): %w", resp.StatusCode, err)
}
if result.Code == 0 && result.AccessToken != "" {
return result.AccessToken, nil
}
// Transient/server-side failures stay untyped so probe callers stay silent and
// retryers can back off; only deterministic client rejections are typed. Covers
// 5xx and the OAuth transient error strings (server_error,
// temporarily_unavailable, slow_down). HTTP 429 was already returned above
// as a typed rate-limit error with retry guidance and an upstream delay when available.
if resp.StatusCode >= 500 ||
result.Error == "server_error" || result.Error == "temporarily_unavailable" ||
result.Error == "slow_down" {
return "", fmt.Errorf("TAT endpoint transient failure (HTTP %d, code=%d, error=%q): %s",
resp.StatusCode, result.Code, result.Error, result.ErrorDescription)
}
View on GitHub (pinned to 7fd6ef3c07)
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.
Example fix
// before (caller sees only the wrapped error)
token, err := credential.FetchTAT(ctx, cfg)
// after (surface the raw body for diagnosis when parsing fails)
token, err := credential.FetchTAT(ctx, cfg)
if err != nil {
if strings.Contains(err.Error(), "failed to parse TAT response") {
log.Printf("TAT parse failure, check proxy/gateway for non-JSON response (status embedded in error): %v", err)
}
} Defensive patterns
Strategy: retry
Validate before calling
// Optionally pre-flight the endpoint shape:
resp, err := http.Post(tokenURL, "application/json", body)
if err == nil && resp.StatusCode == 200 {
var probe struct{ AccessToken string `json:"access_token"` }
raw, _ := io.ReadAll(io.LimitReader(resp.Body, 1<<16))
if json.Unmarshal(raw, &probe) != nil || !strings.HasPrefix(strings.TrimSpace(string(raw)), "{") {
log.Printf("endpoint returning non-JSON body: %.200s", raw)
}
} Try / catch
token, err := credential.FetchTAT(ctx, cfg)
if err != nil {
if strings.Contains(err.Error(), "failed to parse TAT response") {
// ambiguous: likely proxy/gateway noise — retry after short delay
time.Sleep(time.Second)
token, err = credential.FetchTAT(ctx, cfg)
}
if err != nil { return fmt.Errorf("obtain TAT: %w", err) }
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "Invalid JSON response" and "Failed to parse response" errors: when an API answers 200 but the body isn't the JSON your library expected — this error's family across 28 libraries.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- failed to parse response: %w
- app registration failed: HTTP %d – response not JSON
- Device authorization failed: HTTP %d – response not JSON
- response parse error: %w (body: %s)
- TAT response missing access_token (HTTP %d)
AI-assisted analysis of larksuite/cli@7fd6ef3c07 (2026-09-04).
Data as JSON: /api/errors/128aae7ae56a131a.
Report an issue: GitHub.