{"record":{"id":"7cdcd4dd10193cbb","repo":"can1357/oh-my-pi","slug":"sso-token-missing","errorCode":"sso-token-missing","errorMessage":"AWS SSO token for ${startUrl} not found in ~/.aws/sso/cache. Run 'aws sso login' first.","messagePattern":"AWS SSO token for (.+?) not found in ~/\\.aws/sso/cache\\. Run 'aws sso login' first\\.","errorType":"error_code","errorClass":"AIError.AwsCredentialsError","httpStatus":null,"severity":"error","filePath":"packages/ai/src/providers/aws-credentials.ts","lineNumber":436,"sourceCode":"): Promise<ResolvedCredentials | undefined> {\n\t// Two SSO profile shapes:\n\t//   - legacy: `sso_start_url` + `sso_region` directly on the profile\n\t//   - sso-session: `sso_session = my-session` references a `[sso-session my-session]` block\n\tlet startUrl = profileCfg.sso_start_url;\n\tlet ssoRegion = profileCfg.sso_region;\n\tconst sessionName = profileCfg.sso_session;\n\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\",","sourceCodeStart":418,"sourceCodeEnd":454,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/ai/src/providers/aws-credentials.ts#L418-L454","documentation":"Thrown when an AWS SSO-based profile requires a bearer token but no cached SSO token with an access token exists in ~/.aws/sso/cache for the profile's start URL. The library only reads pre-cached tokens created by the AWS CLI; it never initiates an SSO login flow. Without the token it cannot call the SSO portal federation API to get role credentials.","triggerScenarios":"readSsoCredentials() resolves a profile with sso_start_url/sso_region (or an sso_session block), calls loadSsoCachedToken(), and gets undefined or a token without accessToken — i.e. `aws sso login` was never run, the cache directory doesn't exist, or the cache was cleared.","commonSituations":"Fresh machine or CI container where ~/.aws/sso/cache was never populated; user logged into a different start URL than the profile's; running as a different user so HOME points elsewhere; cache wiped by cleanup scripts; using an sso-session whose cached token file was deleted.","solutions":["Run `aws sso login --profile <profile>` (or `aws sso login` with the matching sso-session) to populate ~/.aws/sso/cache, then retry","Verify the profile's sso_start_url / sso_session matches the one you actually logged in to","Check ~/.aws/sso/cache exists and contains JSON files with accessToken, and that you run under the same HOME user","In CI, run `aws sso login` (with device-code flow or a pre-seeded cache) before using SSO profiles"],"exampleFix":"// before\n// running the app with an SSO profile on a fresh machine -> error\n\n// after\n$ aws sso login --profile my-sso-profile\n$ # then run the app again","handlingStrategy":"validation","validationCode":"import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport * as os from \"node:os\";\n// Run before resolving an SSO profile:\nconst cacheDir = path.join(os.homedir(), \".aws\", \"sso\");\nconst hasToken = fs.existsSync(path.join(cacheDir, \"cache\")) &&\n  fs.readdirSync(path.join(cacheDir, \"cache\")).some(f => {\n    try { return !!JSON.parse(fs.readFileSync(path.join(cacheDir, \"cache\", f), \"utf8\")).accessToken; }\n    catch { return false; }\n  });\nif (!hasToken) throw new Error(\"Run `aws sso login --profile <profile>` first\");","typeGuard":"function hasSsoToken(t: unknown): t is { accessToken: string; expiresAt?: string } {\n  return typeof t === \"object\" && t !== null && typeof (t as { accessToken?: unknown }).accessToken === \"string\";\n}","tryCatchPattern":null,"preventionTips":["Run `aws sso login` for the exact profile/session before using SSO profiles","Keep the same HOME/user between login and application runtime (esp. in containers/CI)","Seed CI caches with a fresh SSO token at pipeline start","Confirm sso_start_url in the profile matches the portal you logged into"],"tags":["aws","sso","authentication","config"],"backgroundTag":"aws-sso-token-missing","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}