{"record":{"id":"e8179913d061d895","repo":"decolua/9router","slug":"failed-to-list-profiles-error","errorCode":null,"errorMessage":"Failed to list profiles: ${error}","messagePattern":"Failed to list profiles: (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/lib/oauth/services/kiro.js","lineNumber":284,"sourceCode":"   */\n  async listAvailableProfiles(accessToken, region = \"us-east-1\") {\n    assertValidAwsRegion(region);\n    const endpoint = `https://codewhisperer.${region}.amazonaws.com`;\n\n    const response = await fetch(endpoint, {\n      method: \"POST\",\n      headers: {\n        \"Content-Type\": \"application/x-amz-json-1.0\",\n        \"x-amz-target\": \"AmazonCodeWhispererService.ListAvailableProfiles\",\n        \"Authorization\": `Bearer ${accessToken}`,\n        \"Accept\": \"application/json\",\n      },\n      body: JSON.stringify({ maxResults: 10 }),\n    });\n\n    if (!response.ok) {\n      const error = await response.text();\n      throw new Error(`Failed to list profiles: ${error}`);\n    }\n\n    const data = await response.json();\n    const profiles = Array.isArray(data?.profiles) ? data.profiles : [];\n    const arnOf = (p) => p?.arn || p?.profileArn || null;\n    const match = profiles.find((p) => arnOf(p)?.split(\":\")[3] === region) || profiles[0];\n    return arnOf(match);\n  }\n\n  /**\n   * Validate an API key against the Amazon Q model catalog. A bearer-only call\n   * to ListAvailableProfiles can return HTTP 200 with an empty list for an\n   * arbitrary key, so it is not proof that the key can run inference.\n   */\n  async listAvailableApiKeyModels(apiKey, region = \"us-east-1\") {\n    assertValidAwsRegion(region);\n    const params = new URLSearchParams({ origin: \"AI_EDITOR\" });\n    const endpoint = `https://q.${region}.amazonaws.com/ListAvailableModels?${params}`;","sourceCodeStart":266,"sourceCodeEnd":302,"githubUrl":"https://github.com/decolua/9router/blob/90b52e06ffd666b7929554211474d01588f6b1f8/src/lib/oauth/services/kiro.js#L266-L302","documentation":"Thrown by KiroService.listAvailableProfiles when the CodeWhisperer ListAvailableProfiles call (POST https://codewhisperer.<region>.amazonaws.com with the access token as Bearer) returns non-2xx; the AWS error body is embedded in the message. Without a profile list, no profileArn can be selected for the OAuth/IDC account.","triggerScenarios":"The x-amz-target=AmazonCodeWhispererService.ListAvailableProfiles request returns !response.ok — expired/invalid access token (401/403), unsupported region for CodeWhisperer, missing entitlement for the account, or AWS 429/5xx.","commonSituations":"Access token expired because refresh wasn't performed before the profile listing; account has no CodeWhisperer/Amazon Q subscription; calling in a region where CodeWhisperer isn't available; network/proxy stripping the Authorization header.","solutions":["Refresh the access token (refreshToken) before calling listAvailableProfiles — expired bearer tokens are the most common cause.","Check the embedded AWS error body: AccessDenied/403 usually means the account lacks a CodeWhisperer/Amazon Q entitlement.","Use a CodeWhisperer-supported region (assertValidAwsRegion already gates it, but the service may still reject it).","Retry with backoff if the body shows throttling or a 5xx."],"exampleFix":"// before: listing profiles with a possibly stale access token\nconst arn = await svc.listAvailableProfiles(account.accessToken, region);\n// after: refresh first if near expiry\nif (account.expiresIn && Date.now()/1000 > account.fetchedAt + account.expiresIn - 60) {\n  account = await svc.refreshToken(account.refreshToken, account.providerSpecificData);\n}\nconst arn = await svc.listAvailableProfiles(account.accessToken, region);","handlingStrategy":"try-catch","validationCode":"function tokenUsable(account) {\n  return typeof account?.accessToken === 'string' && account.accessToken.length > 0 &&\n         (!account.expiresAt || Date.now() < account.expiresAt - 60_000);\n}\nif (!tokenUsable(account)) await doRefresh(account); // refresh before listing profiles","typeGuard":"function hasValidArn(arn) { return typeof arn === 'string' && arn.startsWith('arn:'); }","tryCatchPattern":"try {\n  const arn = await svc.listAvailableProfiles(account.accessToken, region);\n} catch (e) {\n  if (/401|403|Unauthorized|AccessDenied/i.test(e.message)) {\n    const fresh = await svc.refreshToken(account.refreshToken, account.providerSpecificData);\n    return svc.listAvailableProfiles(fresh.accessToken, region); // one retry after refresh\n  }\n  if (/throttl/i.test(e.message)) return retryWithBackoff();\n  throw e;\n}","preventionTips":["Always refresh the access token if it is near or past expiresIn before calling ListAvailableProfiles.","Log the embedded AWS error body — AccessDenied distinguishes entitlement problems from token problems.","Confirm the account actually has a CodeWhisperer/Amazon Q subscription; 403 with a fresh token means it does not.","Retry only throttling/5xx errors with backoff; auth errors need a token refresh or re-login."],"tags":["aws","codewhisperer","oauth","authorization","network"],"backgroundTag":"oauth-upstream-error","analyzedSha":"90b52e06ffd666b7929554211474d01588f6b1f8","analyzedAt":"2026-08-30T21:05:45.952Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}