JuliusBrussee/caveman · error
awscreds: returned incomplete credentials
Error message
awscreds: %s returned incomplete credentials
What it means
awscreds.fetch iterates its credential sources (env, web identity, container, IMDS) and returns the first result. If a source returns a non-nil result whose awssig.Credentials fail Valid() — i.e. missing AccessKeyID, SecretAccessKey, or SessionToken where required — the whole fetch fails with this error instead of returning half-usable credentials.
Solutions
- Check which source produced it — the %s verb names it (web identity, container, IMDS) — and inspect that source's raw response
- Verify environment variables: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY (and AWS_SESSION_TOKEN if using session creds) are all set and non-empty
- Test the metadata endpoint directly (169.254.169.254 / AWS_CONTAINER_CREDENTIALS_RELATIVE_URI) from the same host; a proxy or firewall may be returning junk
- Prefer an explicit, complete source: set full static creds in env or use a valid web identity token file so a healthy source wins
Example fix
// before
creds, err := awscreds.Credentials(ctx, p) // "awscreds: imds returned incomplete credentials"
// after
if os.Getenv("AWS_ACCESS_KEY_ID") == "" || os.Getenv("AWS_SECRET_ACCESS_KEY") == "" {
// fix partial env config before calling
log.Fatal("set both AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY")
}
creds, err := awscreds.Credentials(ctx, p) Defensive patterns
Strategy: fallback
Validate before calling
// sanity-check env creds before relying on the chain
id, sec := os.Getenv("AWS_ACCESS_KEY_ID"), os.Getenv("AWS_SECRET_ACCESS_KEY")
if id != "" && sec == "" { return errors.New("AWS_SECRET_ACCESS_KEY missing while AWS_ACCESS_KEY_ID set") } Type guard
func credsComplete(err error) bool {
return err != nil && !strings.Contains(err.Error(), "returned incomplete credentials")
} Try / catch
creds, err := awscreds.Credentials(ctx, p)
if err != nil {
if strings.Contains(err.Error(), "incomplete credentials") {
// fall back to an explicit static/profile provider
creds = staticCredsFromProfile("default")
}
if creds == nil { return fmt.Errorf("aws credentials: %w", err) }
} Prevention
- Never set AWS_ACCESS_KEY_ID without AWS_SECRET_ACCESS_KEY (and AWS_SESSION_TOKEN when applicable)
- Audit metadata endpoints (IMDS/container) on hosts where proxies run — stub responses cause this
- Prefer one explicit source over the implicit chain in production
- Log which source the chain selected to spot degraded sources early
When it happens
Trigger: Credentials() -> fetch() -> some source (e.g. fromWebIdentity, fromContainer, fromIMDS) parses a response and builds a result whose creds.Valid() is false: empty access key ID, empty secret, or a required session token missing (e.g. role-credential responses missing SessionToken).
Common situations: An IMDS/container metadata endpoint returns malformed or partial JSON; the STS AssumeRoleWithWebIdentity XML parse yields empty fields; a corporate proxy intercepts metadata requests and returns stub data; env vars set partially (only AWS_ACCESS_KEY_ID set, no secret).
Related errors
- AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY must be set…
- awscreds: AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY must…
- awscreds: build container credentials request
- awscreds: no AWS credentials found (env, web identity…
- awscreds: read container authorization token file
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/e73089dd8ed8a184.
Report an issue: GitHub.
Appendix: source
Thrown at shared/platform/awscreds/awscreds.go:270
type result struct {
creds awssig.Credentials
expires time.Time
source string
}
func (p *Provider) fetch(ctx context.Context) (*result, error) {
for _, source := range []func(context.Context) (*result, error){
p.fromEnv, p.fromWebIdentity, p.fromContainer, p.fromIMDS,
} {
res, err := source(ctx)
if err != nil {
return nil, err
}
if res == nil {
continue
}
if !res.creds.Valid() {
return nil, fmt.Errorf("awscreds: %s returned incomplete credentials", res.source)
}
return res, nil
}
return nil, errors.New("awscreds: no AWS credentials found (env, web identity, container, IMDS)")
}
func (p *Provider) env(name string) string { return strings.TrimSpace(p.getenv(name)) }
// fromEnv reads static keys. A half-configured pair is an error, not a skip:
// the operator clearly meant to sign as these keys, and falling through would
// silently sign as whatever ambient role the host carries — a different
// principal, bill, and CloudTrail identity — with no disclosure. A lone
// AWS_SESSION_TOKEN is not a pair and does not trigger this.
func (p *Provider) fromEnv(context.Context) (*result, error) {
access, secret := p.env("AWS_ACCESS_KEY_ID"), p.env("AWS_SECRET_ACCESS_KEY")
if access == "" && secret == "" {
return nil, nil
}View on GitHub (pinned to 3ee70a1026)