{"record":{"id":"c7c73505e70762ac","repo":"can1357/oh-my-pi","slug":"profile","errorCode":"profile","errorMessage":"AWS profile role chain contains a cycle at '${profile}'.","messagePattern":"AWS profile role chain contains a cycle at '(.+?)'\\.","errorType":"exception","errorClass":"AwsCredentialsError","httpStatus":null,"severity":"error","filePath":"packages/ai/src/providers/aws-credentials.ts","lineNumber":224,"sourceCode":"\tconst configIni = loadSharedConfig ? await readIniFile(configPath) : undefined;\n\n\treturn resolveProfileChain(profile, { credentialsIni, configIni, region, signal, fetchImpl }, new Set());\n}\n\n/**\n * Resolve one profile, following `role_arn` chains. A `role_arn` profile derives\n * base credentials from `source_profile` (recursive), `web_identity_token_file`,\n * or `credential_source`, then exchanges them via STS. Non-role profiles resolve\n * directly from static keys, SSO, or `credential_process`. `seen` guards against\n * `source_profile` cycles.\n */\nasync function resolveProfileChain(\n\tprofile: string,\n\tctx: ProfileResolveContext,\n\tseen: Set<string>,\n): Promise<ResolvedCredentials | undefined> {\n\tif (seen.has(profile)) {\n\t\tthrow new AIError.AwsCredentialsError(`AWS profile role chain contains a cycle at '${profile}'.`, \"profile\");\n\t}\n\tseen.add(profile);\n\n\t// Static credentials live in ~/.aws/credentials; SSO/role config lives in\n\t// ~/.aws/config under `[profile foo]`. Merge into a single view.\n\tconst merged: Record<string, string> = {\n\t\t...(ctx.configIni?.[profile] ?? {}),\n\t\t...(ctx.credentialsIni?.[profile] ?? {}),\n\t};\n\tif (Object.keys(merged).length === 0) return undefined;\n\n\tif (merged.role_arn) return assumeRoleFromProfile(profile, merged, ctx, seen);\n\n\tif (merged.aws_access_key_id && merged.aws_secret_access_key) {\n\t\tconst out: ResolvedCredentials = {\n\t\t\taccessKeyId: merged.aws_access_key_id,\n\t\t\tsecretAccessKey: merged.aws_secret_access_key,\n\t\t};","sourceCodeStart":206,"sourceCodeEnd":242,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/ai/src/providers/aws-credentials.ts#L206-L242","documentation":"AIError.AwsCredentialsError with kind 'profile' thrown by resolveProfileChain in packages/ai/src/providers/aws-credentials.ts. When an AWS profile specifies role_arn + source_profile, the resolver follows the chain recursively, tracking visited profiles in a `seen` set; if it reaches a profile already in the set, the configuration is a role-assumption cycle (A assumes via B, B assumes via A) and it throws instead of looping forever.","triggerScenarios":"~/.aws/config defines profiles where source_profile (or the chain via role_arn/source_profile and assumeRoleFromProfile) references back to an ancestor: e.g. [profile a] role_arn=... source_profile=b and [profile b] role_arn=... source_profile=a; resolving credentials with AWS_PROFILE=a (or readProfileCredentials/assumeRoleFromProfile on it) hits the cycle.","commonSituations":"Copy-pasting profile blocks and leaving source_profile pointing at the wrong (circular) profile; renaming profiles so a previously linear chain accidentally loops; generated config from tooling that cross-references two roles; typo where source_profile equals the profile itself.","solutions":["Open ~/.aws/config (and ~/.aws/credentials) and find the cycle: trace source_profile links starting from the profile in the error message","Break the cycle by pointing the last link's source_profile at a profile with static credentials (no role_arn/source_profile)","If two profiles must assume each other's roles, split them: keep one chain one-directional (base -> role1 -> role2)","Rename the ambiguous profile if a copy/paste duplicate exists, then update AWS_PROFILE / caller references"],"exampleFix":"// before (~/.aws/config)\n[profile a]\nrole_arn = arn:aws:iam::111:role/r1\nsource_profile = b\n[profile b]\nrole_arn = arn:aws:iam::222:role/r2\nsource_profile = a\n// after: terminate chain at static creds\n[profile a]\nrole_arn = arn:aws:iam::111:role/r1\nsource_profile = base\n[profile base]\naws_access_key_id = AKIA...\naws_secret_access_key = ...","handlingStrategy":"validation","validationCode":"function assertNoProfileCycle(profiles: Record<string, { source_profile?: string }>, start: string): void {\n  const seen = new Set<string>();\n  let cur: string | undefined = start;\n  while (cur) {\n    if (seen.has(cur)) throw new Error(`AWS profile role chain contains a cycle at '${cur}'`);\n    seen.add(cur);\n    cur = profiles[cur]?.source_profile;\n  }\n}","typeGuard":null,"tryCatchPattern":"try {\n  const result = await session.prompt(bedrockModel, messages);\n} catch (err) {\n  if (err instanceof AIError.AwsCredentialsError && err.kind === \"profile\") {\n    console.error(`Fix ~/.aws/config: ${err.message}`);\n    return;\n  }\n  throw err;\n}","preventionTips":["Before committing ~/.aws/config changes, trace each source_profile link to ensure the chain ends at a static-credential profile","Never set a profile's source_profile to itself or to a profile that transitively points back to it","Use distinct base profiles for each role chain instead of cross-referencing role profiles","Run `aws sts get-caller-identity --profile <name>` after config edits to validate the chain resolves"],"tags":["aws","credentials","profile","configuration","cycle"],"backgroundTag":"aws-profile-role-chain-cycle","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}