{"record":{"id":"12cb07071c9f969a","repo":"can1357/oh-my-pi","slug":"sso-token-expired","errorCode":"sso-token-expired","errorMessage":"AWS SSO token for ${startUrl} has expired. Run 'aws sso login' to refresh.","messagePattern":"AWS SSO token for (.+?) has expired\\. Run 'aws sso login' to refresh\\.","errorType":"error_code","errorClass":"AIError.AwsCredentialsError","httpStatus":null,"severity":"error","filePath":"packages/ai/src/providers/aws-credentials.ts","lineNumber":443,"sourceCode":"\tif (sessionName && configIni) {\n\t\tconst session = configIni[`sso-session:${sessionName}`];\n\t\tif (session) {\n\t\t\tstartUrl = startUrl || session.sso_start_url;\n\t\t\tssoRegion = ssoRegion || session.sso_region;\n\t\t}\n\t}\n\tif (!startUrl || !ssoRegion) return undefined;\n\n\tconst token = await loadSsoCachedToken(startUrl, sessionName);\n\tif (!token?.accessToken) {\n\t\tthrow new AIError.AwsCredentialsError(\n\t\t\t`AWS SSO token for ${startUrl} not found in ~/.aws/sso/cache. Run 'aws sso login' first.`,\n\t\t\t\"sso-token-missing\",\n\t\t);\n\t}\n\tconst expiresAt = token.expiresAt ? Date.parse(token.expiresAt) : Number.POSITIVE_INFINITY;\n\tif (Number.isFinite(expiresAt) && expiresAt <= Date.now()) {\n\t\tthrow new AIError.AwsCredentialsError(\n\t\t\t`AWS SSO token for ${startUrl} has expired. Run 'aws sso login' to refresh.`,\n\t\t\t\"sso-token-expired\",\n\t\t);\n\t}\n\n\tconst url =\n\t\t`https://portal.sso.${ssoRegion}.amazonaws.com/federation/credentials` +\n\t\t`?account_id=${encodeURIComponent(profileCfg.sso_account_id)}` +\n\t\t`&role_name=${encodeURIComponent(profileCfg.sso_role_name)}`;\n\tconst response = await fetchImpl(url, {\n\t\tmethod: \"GET\",\n\t\theaders: { \"x-amz-sso_bearer_token\": token.accessToken },\n\t\tsignal,\n\t});\n\tif (!response.ok) {\n\t\tconst body = await response.text().catch(() => \"\");\n\t\tthrow new AIError.AwsCredentialsError(\n\t\t\t`AWS SSO GetRoleCredentials failed: ${response.status} ${body.slice(0, 200)}`,","sourceCodeStart":425,"sourceCodeEnd":461,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/ai/src/providers/aws-credentials.ts#L425-L461","documentation":"Thrown when a cached AWS SSO token exists in ~/.aws/sso/cache but its expiresAt timestamp is in the past. The library validates the token freshness before using it as a bearer token against the SSO portal federation endpoint and refuses to send an expired token, since the API call would fail with Unauthorized.","triggerScenarios":"readSsoCredentials() loads the cached token, parses token.expiresAt, and Date.parse(expiresAt) <= Date.now() — i.e. the SSO session created by `aws sso login` has lapsed (typically after 8-12 hours or the configured session duration).","commonSituations":"Resuming work the next day after the prior `aws sso login` expired; long-running processes holding a resolved profile past expiry; CI caches a token that expires mid-pipeline; organization shortened the SSO session duration.","solutions":["Run `aws sso login --profile <profile>` to obtain a fresh token, then retry","Increase the SSO session duration in AWS IAM Identity Center / permission set settings if sessions expire too often","For long-running processes, re-resolve credentials near expiry or wrap resolution to re-login/retry on this error","Clear stale entries in ~/.aws/sso/cache only if the CLI doesn't overwrite them on login"],"exampleFix":"// before\n$ ./app --profile corp  # AWS SSO token ... has expired\n\n// after\n$ aws sso login --profile corp\n$ ./app --profile corp","handlingStrategy":"validation","validationCode":"import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport * as os from \"node:os\";\n// Pre-check token freshness before resolving:\nfunction ssoTokenFresh(): boolean {\n  const dir = path.join(os.homedir(), \".aws\", \"sso\", \"cache\");\n  try {\n    return fs.readdirSync(dir).some(f => {\n      try {\n        const t = JSON.parse(fs.readFileSync(path.join(dir, f), \"utf8\"));\n        return !!t.accessToken && (!t.expiresAt || Date.parse(t.expiresAt) > Date.now());\n      } catch { return false; }\n    });\n  } catch { return false; }\n}\nif (!ssoTokenFresh()) throw new Error(\"SSO token expired; run `aws sso login`\");","typeGuard":"function tokenIsValid(t: { expiresAt?: string }): boolean {\n  const exp = t.expiresAt ? Date.parse(t.expiresAt) : Number.POSITIVE_INFINITY;\n  return Number.isFinite(exp) ? exp > Date.now() : true;\n}","tryCatchPattern":"try {\n  creds = await resolveProfileChain(profile);\n} catch (err) {\n  if (err instanceof Error && /has expired/.test(err.message)) {\n    await $`aws sso login --profile ${profile}`;\n    creds = await resolveProfileChain(profile); // retry once with fresh token\n  } else throw err;\n}","preventionTips":["Re-run `aws sso login` at the start of each work session or before long jobs","Increase SSO session duration in IAM Identity Center if it expires too often","For long-running processes, schedule token refresh before the cached expiresAt","Don't cache resolved SSO credentials beyond their expiration"],"tags":["aws","sso","authentication","token-expired","config"],"backgroundTag":"jwt-token-expired","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}