JuliusBrussee/caveman · error
awscreds: returned code
Error message
awscreds: %s returned code %q
What it means
After successfully parsing the metadata JSON, credentialsFromJSON checks the optional 'Code' field. If it is present and not (case-insensitively) "Success", the metadata service reported an application-level error inside a 2xx response (e.g. "BadRequest", "ExpiredToken"), so the library refuses to return credentials. This catches IMDS/ECS responses where the error is carried in the body rather than the HTTP status.
Solutions
- Read the quoted code in the error message; look it up in AWS metadata-service error documentation (e.g. BadRequest means the request path/role is wrong).
- For a just-attached instance profile, wait a few seconds and retry until credentials propagate.
- Verify the role name used in the IMDS path matches a role listed at /latest/meta-data/iam/security-credentials/.
- If persistent, re-check the task/instance IAM configuration in the AWS console.
Defensive patterns
Strategy: retry
Validate before calling
var probe struct{ Code string `json:"Code"` }
if json.Unmarshal(body, &probe) == nil && probe.Code != "" && !strings.EqualFold(probe.Code, "Success") {
// treat as transient for BadRequest right after role attach
} Try / catch
creds, err := provider.Credentials(ctx)
if err != nil && strings.Contains(err.Error(), "returned code") {
// log code, backoff and retry a limited number of times
return retryWithBackoff(ctx, func() error { _, err = provider.Credentials(ctx); return err })
} Prevention
- After attaching an instance profile, wait for propagation before first credential fetch.
- Confirm the role name at /latest/meta-data/iam/security-credentials/ before requesting its credentials.
- Keep task/instance IAM roles valid and not deleted mid-flight.
When it happens
Trigger: fromContainer or fromIMDS gets JSON whose Code field is set to something other than Success, e.g. the IMDS credentials document returns Code="BadRequest" or an ECS agent error code embedded in a 200 response.
Common situations: Instance profile credentials not yet propagated right after attaching a role; ECS task role temporarily unavailable; requesting a role name that no longer exists while the service still returns 200 with an error code.
Related errors
- awscreds: credential expiry
- awscreds: : http
- awscreds: returned an unparseable response
- AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY must be set…
- awscreds: AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY must…
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/8b5c6964e64f4322.
Report an issue: GitHub.
Appendix: source
Thrown at shared/platform/awscreds/awscreds.go:624
}
defer resp.Body.Close()
body, err := io.ReadAll(io.LimitReader(resp.Body, maxBody))
if err != nil {
return nil, fmt.Errorf("awscreds: read %s response: %w", what, err)
}
if resp.StatusCode < 200 || resp.StatusCode > 299 {
return nil, fmt.Errorf("awscreds: %s: http %d", what, resp.StatusCode)
}
return body, nil
}
func credentialsFromJSON(body []byte, source string) (*result, error) {
var parsed credentialJSON
if err := json.Unmarshal(body, &parsed); err != nil {
return nil, fmt.Errorf("awscreds: %s returned an unparseable response", source)
}
if parsed.Code != "" && !strings.EqualFold(parsed.Code, "Success") {
return nil, fmt.Errorf("awscreds: %s returned code %q", source, parsed.Code)
}
expires, err := parseExpiry(parsed.Expiration)
if err != nil {
return nil, fmt.Errorf("awscreds: %s credential expiry: %w", source, err)
}
return &result{
creds: awssig.Credentials{
AccessKeyID: strings.TrimSpace(parsed.AccessKeyID),
SecretAccessKey: strings.TrimSpace(parsed.SecretAccessKey),
SessionToken: strings.TrimSpace(parsed.Token),
},
expires: expires,
source: source,
}, nil
}
// parseExpiry maps an absent expiry to the zero time, which means "never
// refresh" to the cache.View on GitHub (pinned to 3ee70a1026)