can1357/oh-my-pi · error · AwsCredentialsError
${source} response has a missing or invalid Expiration.
Error message
${source} response has a missing or invalid Expiration. What it means
AIError.AwsCredentialsError (kind from the caller: container/task credentials or similar) thrown by requireDynamicCredentialExpiration in packages/ai/src/providers/aws-credentials.ts. Some AWS credential sources (ECS container credentials, EC2 IMDS) are required to include an Expiration field; this helper parses it with Date.parse and throws if it is absent or unparseable, because expiry-based caching and refresh cannot work without it.
Source
Thrown at packages/ai/src/providers/aws-credentials.ts:69
* TTL for file-sourced credentials that carry a session token but no expiry.
* Tools like aws-vault/saml2aws rewrite ~/.aws/credentials with short-lived STS
* session keys; caching them forever serves stale creds after rotation.
*/
const FILE_SESSION_CREDS_TTL_MS = 5 * 60_000;
/**
* Bound for the detached (signal-free) shared resolution: a hung
* credential_process/SSO/IMDS fetch must not pin the inflight slot forever.
*/
const SHARED_RESOLVE_TIMEOUT_MS = 30_000;
function requireDynamicCredentialExpiration(
value: string | undefined,
source: string,
kind: AIError.AwsCredentialsErrorKind,
): number {
const expiresAt = value ? Date.parse(value) : Number.NaN;
if (Number.isFinite(expiresAt)) return expiresAt;
throw new AIError.AwsCredentialsError(`${source} response has a missing or invalid Expiration.`, kind);
}
/** Credential-process expiry is optional; missing/malformed values disable caching. */
function dynamicCredentialExpiration(value: string | undefined): number {
if (!value) return Date.now();
const expiresAt = Date.parse(value);
return Number.isFinite(expiresAt) ? expiresAt : Date.now();
}
interface CacheEntry {
creds: ResolvedCredentials;
expiresAt: number;
}
const cache: Map<string, CacheEntry> = new Map();
const inflight: Map<string, Promise<ResolvedCredentials>> = new Map();
function credentialCacheKey(profile: string, region: string, loadSharedConfig: boolean): string {View on GitHub (pinned to 9690622007)
Solutions
- Fix the credentials endpoint to return a valid Expiration field in ISO-8601 format (e.g. "2026-08-30T12:00:00Z")
- If using a custom/mock ECS credentials server, add the Expiration field to its JSON response
- Bypass the broken container-credentials path by providing static credentials via AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY or an AWS profile
- Check that AWS_CONTAINER_CREDENTIALS_RELATIVE_URI / AWS_CONTAINER_CREDENTIALS_FULL_URI points at the real ECS/EKS metadata service, not a stub
Example fix
// before (custom ECS credentials endpoint response)
{ "AccessKeyId": "AKIA...", "SecretAccessKey": "...", "Token": "..." }
// after
{ "AccessKeyId": "AKIA...", "SecretAccessKey": "...", "Token": "...", "Expiration": "2026-08-30T12:00:00Z" } Defensive patterns
Strategy: fallback
Validate before calling
async function hasParseableExpiration(credsUrl: string): Promise<boolean> {
try {
const json = await fetch(credsUrl).then(r => r.json());
return typeof json.Expiration === "string" && !Number.isNaN(Date.parse(json.Expiration));
} catch {
return false;
}
} Type guard
function hasValidExpiration(v: unknown): v is string {
return typeof v === "string" && Number.isFinite(Date.parse(v));
} Try / catch
try {
const result = await session.prompt(bedrockModel, messages);
} catch (err) {
if (err instanceof AIError.AwsCredentialsError && err.message.includes("invalid Expiration")) {
// fall back to static env credentials or fix the container credentials endpoint
logger.error("credentials endpoint returned bad Expiration", {});
}
throw err;
} Prevention
- When mocking or proxying ECS/IMDS credentials endpoints, always include an ISO-8601 Expiration field
- Point AWS_CONTAINER_CREDENTIALS_* at the real metadata service, not hand-rolled stubs
- Prefer static env keys or a profile in local/dev environments where no metadata service exists
- Validate a custom credentials proxy's JSON schema before deploying it
When it happens
Trigger: readContainerCredentials (ECS/HTTP credentials endpoint) or expiresAt receives a credentials response whose Expiration field is missing, empty, or not a parseable ISO-8601 date string, e.g. a mocked/misconfigured AWS_CONTAINER_CREDENTIALS_RELATIVE_URI endpoint returning JSON without Expiration.
Common situations: Localstack or a corporate proxy replacing the ECS metadata endpoint returns minimal JSON without Expiration; a custom credentials proxy emits a non-ISO date format; an IMDS/ECS endpoint behind a gateway returns an error page or partial JSON; clock/encoding issues aside, typo'd field name in a hand-rolled credentials server.
Related errors
- Unable to resolve AWS credentials. Configure static environm
- profile
- container
- Bedrock HTTP ${response.status}: ${errBody.slice(0, 1000)}
- Bedrock response has no body
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/1ec45ac0442d0ea6.
Report an issue: GitHub.