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

  1. Fix the credentials endpoint to return a valid Expiration field in ISO-8601 format (e.g. "2026-08-30T12:00:00Z")
  2. If using a custom/mock ECS credentials server, add the Expiration field to its JSON response
  3. Bypass the broken container-credentials path by providing static credentials via AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY or an AWS profile
  4. 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 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


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/1ec45ac0442d0ea6. Report an issue: GitHub.