grpc/grpc-go · error
http status %d, body: %s
Error message
http status %d, body: %s
What it means
Returned by sendRequest in sts/sts.go:320 when the STS HTTP exchange returns a non-2xx status. The full response body is included so the caller can see the token server's error. This is the runtime signal that the token-exchange endpoint rejected the request (auth, scope, subject token, etc.) or was unavailable.
Source
Thrown at credentials/sts/sts.go:320
resp, err := client.Do(req)
if err != nil {
return nil, err
}
// When the http.Client returns a non-nil error, it is the
// responsibility of the caller to read the response body till an EOF is
// encountered and to close it.
body, err := io.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
return nil, err
}
if resp.StatusCode == http.StatusOK {
return body, nil
}
logger.Warningf("http status %d, body: %s", resp.StatusCode, string(body))
return nil, fmt.Errorf("http status %d, body: %s", resp.StatusCode, string(body))
}
func tokenInfoFromResponse(respBody []byte) (*tokenInfo, error) {
respData := &responseParameters{}
if err := json.Unmarshal(respBody, respData); err != nil {
return nil, fmt.Errorf("json.Unmarshal(%v): %v", respBody, err)
}
if respData.AccessToken == "" {
return nil, fmt.Errorf("empty accessToken in response (%v)", string(respBody))
}
return &tokenInfo{
tokenType: respData.TokenType,
token: respData.AccessToken,
expiryTime: time.Now().Add(time.Duration(respData.ExpiresIn) * time.Second),
}, nil
}
// requestParameters stores all STS request attributes defined inView on GitHub (pinned to 03255a9237)
Solutions
- Read the response body in the error string to identify the OAuth2 error (invalid_grant, invalid_scope, unauthorized_client).
- Verify SubjectTokenPath contents are fresh and match SubjectTokenType; refresh the mounted token.
- Confirm Audience/Scope/Resource match what the STS server expects.
- For transient 5xx, retry with backoff (the STS creds do not retry automatically).
Example fix
// before
opts := sts.Options{
SubjectTokenPath: "/var/run/secrets/expired-token",
Audience: "wrong-audience",
}
// after
opts := sts.Options{
SubjectTokenPath: "/var/run/secrets/fresh-identity-token",
SubjectTokenType: "urn:ietf:params:oauth:token-type:jwt",
Audience: "https://my-grpc-server",
} Defensive patterns
Strategy: retry
Validate before calling
// Before relying on STS, ensure subject/actor token files are fresh and readable.
if _, err := os.Stat(opts.SubjectTokenPath); err != nil { return err }
b, err := os.ReadFile(opts.SubjectTokenPath)
if err != nil || len(bytes.TrimSpace(b)) == 0 { return errors.New("subject token missing") } Try / catch
if st, ok := status.FromError(err); ok && st.Code() == codes.Unavailable {
if strings.Contains(st.Message(), "http status") {
// parse body for OAuth2 error (invalid_grant/invalid_scope/unauthorized_client)
// refresh subject token or fix audience/scope; for 5xx, back off and retry
}
} Prevention
- Mount fresh, short-lived subject tokens via a sidecar/volume refresh.
- Match Audience/Scope/SubjectTokenType to the STS server's expectations.
- For transient 5xx, implement a backoff retry around the RPC or a custom PerRPCCreds wrapper.
When it happens
Trigger: The subject token file is expired or wrong; audience/scope mismatch; the STS endpoint requires different SubjectTokenType; network path returns 4xx/5xx (401, 403, 400, 500, 502); subject/actor token file unreadable upstream.
Common situations: Workload identity token expired; misconfigured audience in xDS bootstrap; service-account token audience not matching the STS server; STS endpoint behind a proxy that returns HTML errors; transient 5xx during an outage.
Related errors
- empty accessToken in response (%v)
- unable to transfer STS PerRPCCredentials: %v
- failed to create http request: %v
- json.Unmarshal(%v): %v
- empty token_exchange_service_uri in options
AI-assisted analysis of grpc/grpc-go@03255a9237 (2026-08-07).
Data as JSON: /api/errors/ea01982616565e6e.
Report an issue: GitHub.