{"record":{"id":"abe4eeb01e970564","repo":"decolua/9router","slug":"failed-to-list-models-error","errorCode":null,"errorMessage":"Failed to list models: ${error}","messagePattern":"Failed to list models: (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/lib/oauth/services/kiro.js","lineNumber":375,"sourceCode":"    const target = \"AmazonCodeWhispererService.ListAvailableModels\";\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\": target,\n        \"Authorization\": `Bearer ${accessToken}`,\n        \"Accept\": \"application/json\",\n      },\n      body: JSON.stringify({\n        origin: \"AI_EDITOR\",\n        profileArn,\n      }),\n    });\n\n    if (!response.ok) {\n      const error = await response.text();\n      throw new Error(`Failed to list models: ${error}`);\n    }\n\n    const data = await response.json();\n    return (data.models || []).map(m => ({\n      id: m.modelId,\n      name: m.modelName || m.modelId,\n      description: m.description,\n      rateMultiplier: m.rateMultiplier,\n      rateUnit: m.rateUnit,\n      maxInputTokens: m.tokenLimits?.maxInputTokens || 0,\n    }));\n  }\n\n  /**\n   * Fetch user email from access token (optional, for display)\n   */\n  extractEmailFromJWT(accessToken) {\n    try {","sourceCodeStart":357,"sourceCodeEnd":393,"githubUrl":"https://github.com/decolua/9router/blob/90b52e06ffd666b7929554211474d01588f6b1f8/src/lib/oauth/services/kiro.js#L357-L393","documentation":"listAvailableModels POSTs to the CodeWhisperer AmazonCodeWhispererService.ListAvailableModels endpoint with the access token and profileArn. If the upstream responds with a non-2xx status, the response body (an AWS JSON error document) is read as text and rethrown as 'Failed to list models: <body>'. The thrown message therefore carries the authoritative AWS error (e.g. AccessDeniedException, expired token).","triggerScenarios":"Calling listAvailableModels(accessToken, profileArn) where the POST to https://codewhisperer.us-east-1.amazonaws.com returns response.ok === false — expired/invalid access token, missing or wrong profileArn, insufficient permissions, or endpoint unavailability (5xx).","commonSituations":"OAuth access token expired and was not refreshed; profileArn omitted, null, or belonging to a different region/account; token from a different auth method being used against CodeWhisperer; AWS-side outage or throttling.","solutions":["Read the AWS error body after 'Failed to list models: ' — AccessDenied/Unauthorized means refresh the access token (or re-run OAuth) before retrying.","Verify profileArn is correct and non-null; fetch it via listAvailableProfiles if unknown.","Refresh/re-authenticate the credential to obtain a valid accessToken for the CodeWhisperer surface.","Retry with backoff if the body indicates throttling or a 5xx server error."],"exampleFix":"// before\nconst models = await kiro.listAvailableModels(staleToken, profileArn);\n// after\nconst fresh = await refreshTokenIfNeeded(staleToken);\nconst models = await kiro.listAvailableModels(fresh, profileArn);","handlingStrategy":"try-catch","validationCode":"if (!accessToken || typeof accessToken !== \"string\") {\n  throw new Error(\"A valid access token is required before listing models\");\n}\nif (!profileArn) {\n  profileArn = await kiro.resolveProfileArn(accessToken); // obtain if unknown\n}","typeGuard":"function hasValidCredentials(cred) {\n  return typeof cred?.accessToken === \"string\" && cred.accessToken.length > 0 &&\n    (cred.profileArn == null || typeof cred.profileArn === \"string\");\n}","tryCatchPattern":"try {\n  const models = await kiro.listAvailableModels(token, profileArn);\n} catch (e) {\n  if (e.message.startsWith(\"Failed to list models:\")) {\n    if (/AccessDenied|Unauthorized|expired/i.test(e.message)) {\n      // refresh token / re-run OAuth, then retry once\n    } else if (/Throttl|ServiceUnavailable|5\\d\\d/.test(e.message)) {\n      // retry with exponential backoff\n    }\n  }\n  throw e;\n}","preventionTips":["Refresh access tokens proactively before expiry instead of on failure.","Always pass a valid profileArn; resolve it from listAvailableProfiles when unknown.","Retry with backoff on 5xx/throttling bodies only.","Log the AWS error body (after the prefix) for diagnosis — it names the exact AWS exception."],"tags":["network","aws","kiro","oauth","http-error","access-token"],"backgroundTag":"upstream-http-error","analyzedSha":"90b52e06ffd666b7929554211474d01588f6b1f8","analyzedAt":"2026-08-30T21:05:45.952Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}