Repository navigation
[Bug]: Kiro provider profileArn required 400 for Builder ID accounts on gated models #993
Description
Summary
Kiro's GenerateAssistantResponse backend rejects requests from AWS Builder
ID / social-login accounts with 400 ValidationException: profileArn is required for this request for at least claude-opus-5 and
claude-sonnet-5. No local store ever caches a profileArn for that account
type, resolveKiroProfileArn() in src/oauth/kiro.ts only reads a cached
value, and src/adapters/kiro.ts's build() silently omits profileArn
from the wire payload when nothing was cached — so every request from a
Builder ID account against those models fails.
I expected the request to either succeed (matching what the official
kiro-cli does for the same account) or fail with a clearer, provider-level
error rather than a raw upstream ValidationException.
Investigation update — a candidate fix I proposed below turned out to be
insufficient; see "What I tried" for why. Filing as a bug report with the
open question rather than a verified fix.
Reproduction
- Configure the
kiroprovider withauthMode: oauth, importing a
Builder-ID (not IAM Identity Center)kiro-clisession — not an API key
(ksk_...) login. POST /v1/chat/completionswith"model": "kiro/claude-opus-5"(or
claude-sonnet-5) and any prompt.- Observe:
Provider error 400: Kiro invalid request:
com.amazon.kiro.runtimeservice#ValidationException: profileArn is required for this request.
Debug logging (OCX_DEBUG=1) on the failing request:
[ocx:kiro:request] {"region":"us-east-1","requestedModel":"claude-opus-5","completionMode":"disabled","bodyBytes":362,"messageCount":1,"toolCount":0,"hasProfileArn":false,"wireClient":"cli","hasPreviousResponseId":false}
For comparison, the official kiro-cli (v2.16.0) against the same account
succeeds on the same model:
$ kiro-cli chat --model claude-opus-5 --effort high --no-interactive "Say OK"
> OK
▸ Credits: 0.25 • Time: 2s
Root cause
src/oauth/kiro.ts — resolveKiroProfileArn():
export function resolveKiroProfileArn(account?: Pick<KiroOAuthMetadata, "profileArn">): string | undefined {
if (account !== undefined) return account.profileArn;
const env = process.env.KIRO_PROFILE_ARN;
if (env) return env;
return readImportedKiroCredential()?.profileArn;
}This only returns a profileArn if one is already cached in account
metadata, the KIRO_PROFILE_ARN env var, or the locally imported kiro-cli
credential. For a Builder ID account none of these exist — the OAuth account
metadata opencodex stores for Kiro is {ssoRegion, apiRegion, clientId, clientSecret} with no profileArn field, and the local kiro-cli SQLite
store (~/.local/share/kiro-cli/data.sqlite3) has no row for
api.codewhisperer.profile either.
src/adapters/kiro.ts (build(), ~line 1699) treats the absence of a
cached profileArn as proof the account doesn't need one:
const resolvedProfileArn = resolveKiroProfileArn(parsed._kiroAuthContext);
const isApiKey = provider.apiKey.trim().startsWith("ksk_");
const profileArn = isApiKey ? undefined : resolvedProfileArn;
const wireClient: KiroWireClient = isApiKey || !profileArn ? "cli" : "ide";
...
if (profileArn) payload.profileArn = profileArn;That assumption holds for most models but not for the ones Kiro gates
server-side.
What I tried (fix candidate — insufficient, do not merge as-is)
Initial (wrong) theory: strings on the kiro-cli 2.16.0 binary surfaced
a literal profile ARN compiled into
fig_api_client::profile_resolver::ProfileResolver::for_builder_id, and a
trace-level (KIRO_LOG_LEVEL=trace) kiro-cli run showed that exact ARN
attached to every outgoing request on my test account, including
ListAvailableModels. No ListAvailableProfiles RPC ever fires in the
trace on either run described below. I concluded this was a single
constant, shared by every Builder ID account, and proposed hardcoding it as
a fallback in the adapter (diff below, KIRO_PROFILE_PLACEHOLDER stands in
for the literal value from that binary).
Falsified by a second, independent account. I logged a second,
unrelated Builder ID/social-login account into kiro-cli 2.16.0 on a
separate machine and ran the same trace. That account's kiro-cli attached
a different profile ARN — different AWS account-id component and
different profile-id component — from the one on the first account, again
with no ListAvailableProfiles call in the trace. That rules out "one
shared constant for all Builder ID accounts." The mechanism kiro-cli uses
to arrive at a per-account profile with no visible RPC is still unknown to
me — possibly derived from claims already present in the OAuth/OIDC token,
possibly returned inline during token exchange rather than a separate call.
I have not yet confirmed either.
Net: the "hardcode a shared default ARN" fix is wrong. It would send one
account's real profile ARN as a blanket fallback for every other Builder ID
account, which is not obviously safer than omitting profileArn entirely
(today's behavior) and could plausibly cause Kiro's backend to associate a
request with the wrong profile rather than just reject it.
Diff as proposed (do not merge without resolving the above):
--- a/src/adapters/kiro.ts
+++ b/src/adapters/kiro.ts
@@ constants near line 55
const KIRO_FALLBACK_SERIALIZATION_ENVELOPE_BYTES = 64 * 1024;
+const KIRO_BUILDER_ID_DEFAULT_PROFILE_ARN = "<KIRO_PROFILE_PLACEHOLDER>";
type KiroWireClient = "ide" | "cli";
@@ inside build(), ~line 1697
const region = resolveKiroApiRegion(parsed._kiroAuthContext);
const resolvedProfileArn = resolveKiroProfileArn(parsed._kiroAuthContext);
const isApiKey = provider.apiKey.trim().startsWith("ksk_");
- const profileArn = isApiKey ? undefined : resolvedProfileArn;
// wireClient must key off the ORIGINAL lookup, not the fallback below.
- const wireClient: KiroWireClient = isApiKey || !profileArn ? "cli" : "ide";
+ const wireClient: KiroWireClient = isApiKey || !resolvedProfileArn ? "cli" : "ide";
+ const profileArn = isApiKey ? undefined : (resolvedProfileArn ?? KIRO_BUILDER_ID_DEFAULT_PROFILE_ARN);Client or integration
Direct HTTP/API client (also reachable via Codex CLI once opencodex
injects its openai_base_url)
Area
Provider adapter / Authentication and account pool
Version
2.10.0 (@bitkyc08/opencodex)
Operating system
Linux (Ubuntu, x86_64) — reproduced on two separate machines/accounts, same
OS family
Provider and model
kiro / claude-opus-5, claude-sonnet-5 (confirmed affected); a model not
requiring a profileArn (gpt-5.6-terra) shows no equivalent failure on the
same account, so this is model-specific on Kiro's side, not account-wide.
Logs or error output
Failing request (before any change):
$ curl -s -X POST http://127.0.0.1:PORT/v1/chat/completions \
-d '{"model":"kiro/claude-opus-5","messages":[{"role":"user","content":"Say OK"}],"reasoning_effort":"high"}'
{"error":{"message":"Provider error 400: Kiro invalid request: com.amazon.kiro.runtimeservice#ValidationException: profileArn is required for this request.","type":"invalid_request_error","param":null,"code":"invalid_request_error"}}
[ocx:kiro:request] {"region":"us-east-1","requestedModel":"claude-opus-5","completionMode":"disabled","bodyBytes":362,"messageCount":1,"toolCount":0,"hasProfileArn":false,"wireClient":"cli","hasPreviousResponseId":false}
Same request, same account, with the candidate fix applied (succeeds, but
see "What I tried" for why this specific fix is not safe to merge):
$ curl -s -X POST http://127.0.0.1:PORT/v1/chat/completions \
-d '{"model":"kiro/claude-opus-5","messages":[{"role":"user","content":"Say OK"}],"reasoning_effort":"high"}'
{"id":"chatcmpl-...","object":"chat.completion","model":"kiro/claude-opus-5","choices":[{"index":0,"message":{"role":"assistant","content":"OK"},"finish_reason":"stop","logprobs":null}],"usage":{"prompt_tokens":9,"completion_tokens":1,"total_tokens":10,"prompt_tokens_details":{"cached_tokens":0},"completion_tokens_details":{"reasoning_tokens":0}}}
[ocx:kiro:request] {"region":"us-east-1","requestedModel":"claude-opus-5","completionMode":"disabled","bodyBytes":466,"messageCount":1,"toolCount":0,"hasProfileArn":true,"wireClient":"cli","hasPreviousResponseId":false}
[ocx:kiro:attempt_complete] {"mode":"disabled","sawText":true,"sawReasoning":false,"sawRealTool":false,"completionCalls":0,"nativeCompletionStop":true,"stopReason":"END_TURN","assistantChars":2}
Redacted configuration
{
"adapter": "kiro",
"baseUrl": "https://runtime.us-east-1.kiro.dev",
"authMode": "oauth",
"defaultModel": "kiro-auto"
}Checks
- I searched existing issues and documentation.
- I removed secrets, tokens, account details, request credentials, and
personal data. Specifically: no AWS account IDs, no Kiro profile IDs, no
OAuth/access/refresh tokens, no email addresses, no hostnames/ports/paths
from any machine used to reproduce this appear anywhere above — all such
values are replaced with placeholders (<AWS_ACCOUNT_ID_PLACEHOLDER>,
<KIRO_PROFILE_PLACEHOLDER>,PORT) even where they were plausible
evidence for the bug, because two independently-tested accounts produced
two different values for both fields and neither is reliably distinguishable
from account-identifying data without a byte-level audit I'm not confident
in giving a hard guarantee on.
Activity
github-actions commentedon Aug 4, 2026
Automated translation bookkeeping — detected language: unknown.
github-actions commentedon Aug 4, 2026
Issue reopened
The report now contains the information required by the automated check. Thanks for updating it.
[-]Kiro provider: profileArn is required 400 for Builder ID accounts on gated models (fix included, verified)[/-][+][Bug]: Kiro provider profileArn required 400 for Builder ID accounts on gated models[/+]redeye1011 commentedon Aug 4, 2026
@lidge-jun — flagging a mistake I made on this issue that needs your admin access to fully fix.
In my first version of this report (posted, then edited), I included a real AWS account ID and Kiro profile ID extracted from my own account while investigating this bug. I later redacted them from the current body, but I've since confirmed GitHub retains that original text in the issue's public edit history — anyone can view it via the "edited" trail or the API. I don't have repo admin access to purge that history myself.
Could you delete this issue outright, or otherwise scrub the edit history, so that original revision isn't publicly retrievable? I can re-file a clean version of the bug report (already prepared, fully redacted) once the old history is gone — happy to do that immediately after.
For context on the report itself: root cause is confirmed (Kiro's
GenerateAssistantResponserejects Builder ID/social-login accounts with noprofileArnfor at leastclaude-opus-5/claude-sonnet-5), but the specific fix I proposed turned out to be wrong — testing against a second, independent account showed the profile ARN is per-account, not a shared constant, so a hardcoded fallback isn't safe. That part is accurately reflected in the current body; the privacy issue above is separate and is what I need your help with.Apologies for the mess — appreciate you taking a look.
Merge pull request #1032 from lidge-jun/codex/lane-10-kiro-profile-arn
fix(kiro): capture profileArn from whoami and classify profileArn-req…
fix(kiro): bind whoami identity to a revalidated unchanged CLI session (
lidge-jun commentedon Aug 5, 2026
Fixed on
devin #1032 (merged asdddc674ca).profileArnis captured fromkiro-cli whoami --format jsonwhen the SQLite import lacks one, and aprofileArn-required 400 is classified as its own non-retryable error instead of being retried blindly.The identity binding is guarded: if the active CLI session changes between the SQLite read and
whoami, the whole identity is discarded rather than binding account A's token to account B's ARN. An importedprofileArnstays authoritative, and a malformed ARN is ignored without breaking the login.One caveat worth stating: the
whoamiJSON shapes are read from the documented-ish forms, and this was not verified against a live Builder ID account. If yourwhoamioutput uses a different shape and the ARN still comes up missing, please reopen with a redactedkiro-cli whoami --format jsonand I will match the real contract.Not in a release yet. This is on
dev, andmainis atv2.10.0. It ships in the next release.fix(kiro): capture profileArn from whoami and classify profileArn-req…
fix(kiro): bind whoami identity to a revalidated unchanged CLI session (
Merge pull request lidge-jun#1032 from lidge-jun/codex/lane-10-kiro-p…