Skip to content

fix(kiro): capture profileArn from whoami and classify profileArn-required 400s (#993) - #1032

Merged
lidge-jun merged 3 commits into
devfrom
codex/lane-10-kiro-profile-arn
Aug 5, 2026
Merged

lidge-jun merged 3 commits into
devfrom
codex/lane-10-kiro-profile-arn

Conversation

@lidge-jun

@lidge-jun lidge-jun commented Aug 4, 2026

Copy link
Copy Markdown
Owner

Bug-stack campaign lane (devlog/_plan/260805_bug_stack_campaign/110). Fixes #993.

Builder ID imports often lack a profileArn in SQLite, so gated models 400 with no actionable path.

  • The import now reads the documented whoami --format json surface: narrow shapes only, structural ARN validation, imported SQLite ARN stays authoritative, fail closed when absent. A concurrent session switch between the SQLite read and whoami is revalidated (refresh, or access when refresh is absent) so account A's token never carries account B's ARN.
  • A profileArn-required upstream rejection classifies as the stable, actionable kiro_profile_required (non-retryable, re-login guidance) instead of the generic validation bucket. No hardcoded/derived ARNs; no administrative profile listing.
  • No live AWS verification — structural tests only (kiro suites 229/229 incl. the mid-flight switch regression); bun run typecheck 0 errors; bun run privacy:scan pass; full suite on ssh lidge 8314/0 (campaign tree).

Summary by CodeRabbit

  • Bug Fixes

    • Improved Kiro sign-in to recognize and retain profile information from supported CLI responses.
    • Added validation to prevent stale or mismatched CLI session data from being imported.
    • Improved handling of missing profileArn errors with clear re-authentication guidance.
    • Prevented unnecessary retries for non-retryable profile configuration errors.
  • Tests

    • Added coverage for profile extraction, session validation, metadata precedence, and error handling.

…uired 400s (#993)

Builder ID imports often lack a profileArn in SQLite. The import now
reads the documented whoami --format json surface (narrow shapes,
structural validation, imported SQLite ARN stays authoritative, fail
closed when absent) and persists it account-scoped. A profileArn-
required upstream rejection classifies as the stable, actionable
kiro_profile_required instead of the generic validation bucket. No live
AWS verification — structural tests only.
#993)

A concurrent external Kiro session switch between the SQLite read and
whoami could attach account B's profileArn to account A's token.
whoami's identity is now accepted only when the session token still
matches the import (refresh, or access when refresh is absent);
a mid-flight switch regression pins the behavior.
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

Kiro profile identity and error handling

Layer / File(s) Summary
Profile-required error classification
src/adapters/kiro-errors.ts, tests/kiro-retry.test.ts, tests/kiro-stream.test.ts
Responses containing profileArn and required are classified as kiro_profile_required, status 400, and non-retryable. Tests verify reauthentication guidance and one upstream request.
CLI profile identity extraction
src/oauth/kiro.ts, tests/kiro-oauth.test.ts
whoami parsing validates profile ARNs from top-level and nested profile.arn fields. Tests cover valid and malformed values.
Credential merge and session revalidation
src/oauth/kiro.ts, tests/kiro-oauth.test.ts
Imported metadata remains authoritative. Matching active sessions accept CLI identity data. Session changes clear the imported email and profile ARN, including access-token-only sessions.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant SQLiteCredential
  participant KiroCLI
  participant oauthCredentialFromImported
  SQLiteCredential->>oauthCredentialFromImported: provide imported credential and session token
  oauthCredentialFromImported->>KiroCLI: request whoami identity
  KiroCLI-->>oauthCredentialFromImported: return email and profile ARN
  oauthCredentialFromImported->>SQLiteCredential: revalidate active session token
  SQLiteCredential-->>oauthCredentialFromImported: confirm matching or changed session
  oauthCredentialFromImported-->>SQLiteCredential: return merged or cleared identity metadata
Loading

Possibly related PRs

Suggested reviewers: ingwannu, wibias, mushikingh

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes both primary changes: importing profileArn from whoami and classifying profileArn-required 400 responses.
Linked Issues check ✅ Passed The implementation satisfies #993 by safely importing validated profileArn data, preventing session mismatches, and returning clear reauthentication guidance when required.
Out of Scope Changes check ✅ Passed All production and test changes directly support #993 by handling profileArn import, session validation, error classification, and regression coverage.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/lane-10-kiro-profile-arn

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added the bug Something isn't working label Aug 4, 2026
@Wibias Wibias linked an issue Aug 4, 2026 that may be closed by this pull request
2 tasks

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d2dd48990c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/oauth/kiro.ts
// whoami's identity only when the session token STILL matches the import —
// refresh token, or access token when refresh is absent.
if (identity.profileArn !== undefined) {
const current = readKiroCliSqliteCredential();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Do not borrow whoami ARNs for override DB imports

When a user imports Kiro from KIROCLI_DB_PATH/KIRO_CLI_DB_FILE, the token came from that override database, but kiro-cli whoami still reports the native CLI session. This revalidation calls readKiroCliSqliteCredential() again, which rereads the override and therefore matches the imported token, so OCX can persist account A's access/refresh token with account B's profileArn and send the wrong profile in later Kiro requests. Skip whoami profile capture for import-only DB selectors, or revalidate against the actual native CLI DB that whoami uses.

Useful? React with 👍 / 👎.

Comment thread src/oauth/kiro.ts
expires: imported.expires,
source: imported.source === "json" ? "credential-file" : "local-cli",
...(imported.profileArn ? { accountId: imported.profileArn } : {}),
...(resolvedProfileArn ? { accountId: resolvedProfileArn } : {}),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve the existing slot when adding an ARN

When an existing Kiro account was saved before it had a profile ARN, its stored identity is usually the email. Returning the new credential with accountId set to the newly discovered ARN makes saveCredential() match on accountId ?? email, so a later ordinary login or ocx account login kiro --reauth for the same email appends a second active account instead of updating the old one; the stale profile-less row remains selectable and still fails gated Kiro models. Reconcile by email when adding the ARN to a previously email-only Kiro account, or avoid promoting the ARN to accountId until the store can merge both identities.

Useful? React with 👍 / 👎.

message: "kiro_profile_required: Kiro requires a CodeWhisperer profileArn for this account and model. Re-login or re-import the matching Kiro account (ocx account login kiro --reauth) so the profile is captured, then retry.",
status: 400,
errorType: "invalid_request_error",
code: "kiro_profile_required",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Propagate the profile-required code on HTTP errors

The new kiro_profile_required code is only carried by stream/parser error events; the ordinary non-stream HTTP path calls safeKiroHttpErrorMessage(), then wraps this response as formatErrorResponse(status, "upstream_error", ...), so the /v1/chat/completions reproduction for this 400 still returns top-level code: "upstream_error" instead of the stable code added here. Return a structured classification to the HTTP wrapper or map this message before formatErrorResponse so non-stream clients can handle the same condition.

Useful? React with 👍 / 👎.

#1032 review blocker. The existing session-switch test asserted only that
accountId and kiro.profileArn were absent — but pre-fix code ignores whoami's
ARN entirely, so those assertions passed against the unfixed implementation
too. It was a shape assertion wearing an activation proof's clothes.

The email is what pre-fix code WOULD have kept, so asserting its absence is
what actually exercises the mismatch path. Adds that, plus the two
refresh-absent cases the implementation's access-token fallback needs:
access-token-only revalidation succeeding, and an access-token-only session
that changes under whoami being rejected.

Verified by reverting src/oauth/kiro.ts to the dev version: all three new
tests fail against pre-fix code and pass against the fix.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/adapters/kiro-errors.ts`:
- Around line 114-122: Update the profile-required detection in the Kiro error
mapping to only apply when status is undefined or 400, preserving event-stream
exceptions while excluding rate-limit responses. Replace the independent
evidence checks with a bounded phrase matching profileArn followed by required,
and add negative tests covering a 429 response and an unrelated required field.

In `@src/oauth/kiro.ts`:
- Around line 285-290: Update the identity revalidation guard in the flow around
readKiroCliSqliteCredential so token comparison runs when either identity.email
or identity.profileArn is present, not only when profileArn exists. Preserve
clearing identity when the imported and current credentials differ, and add a
regression test covering a session switch where whoami returns an email without
a valid profile ARN.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: b3e42c8e-1c3b-4957-8a70-fb09405f9303

📥 Commits

Reviewing files that changed from the base of the PR and between 2a72aa4 and 0338d07.

📒 Files selected for processing (5)
  • src/adapters/kiro-errors.ts
  • src/oauth/kiro.ts
  • tests/kiro-oauth.test.ts
  • tests/kiro-retry.test.ts
  • tests/kiro-stream.test.ts

Comment on lines +114 to +122
if (evidence.includes("profilearn") && evidence.includes("required")) {
return {
message: "kiro_profile_required: Kiro requires a CodeWhisperer profileArn for this account and model. Re-login or re-import the matching Kiro account (ocx account login kiro --reauth) so the profile is captured, then retry.",
status: 400,
errorType: "invalid_request_error",
code: "kiro_profile_required",
retryable: false,
};
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Restrict kiro_profile_required to a required-profile 400 response.

Line 114 matches independent occurrences of profilearn and required. A 429 payload such as profileArn is required; quota exceeded becomes a 400 non-retryable error before the rate-limit branch. An unrelated 400 payload such as profileArn is malformed; clientId is required also gives incorrect re-login guidance.

Require status === undefined || status === 400 so event-stream exceptions remain supported. Match a bound profileArn ... required phrase. Add negative tests for a 429 response and an unrelated required field.

Proposed fix
   const headerType = headerValue(headers, ":exception-type") || headerValue(headers, ":error-type") || "";
   const evidence = [headerType, ...payloadDetails(payloadText), message].join(" ").toLowerCase();
+  const profileArnRequired =
+    /\bprofile\s*arn\b\s+(?:is\s+)?required\b|\brequired\s+(?:for\s+)?profile\s*arn\b/.test(evidence);
   if (isContentLengthError(evidence)) {
     return {
       // ...
     };
   }
-  if (evidence.includes("profilearn") && evidence.includes("required")) {
+  if ((status === undefined || status === 400) && profileArnRequired) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (evidence.includes("profilearn") && evidence.includes("required")) {
return {
message: "kiro_profile_required: Kiro requires a CodeWhisperer profileArn for this account and model. Re-login or re-import the matching Kiro account (ocx account login kiro --reauth) so the profile is captured, then retry.",
status: 400,
errorType: "invalid_request_error",
code: "kiro_profile_required",
retryable: false,
};
}
const profileArnRequired =
/\bprofile\s*arn\b\s+(?:is\s+)?required\b|\brequired\s+(?:for\s+)?profile\s*arn\b/.test(evidence);
if ((status === undefined || status === 400) && profileArnRequired) {
return {
message: "kiro_profile_required: Kiro requires a CodeWhisperer profileArn for this account and model. Re-login or re-import the matching Kiro account (ocx account login kiro --reauth) so the profile is captured, then retry.",
status: 400,
errorType: "invalid_request_error",
code: "kiro_profile_required",
retryable: false,
};
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/adapters/kiro-errors.ts` around lines 114 - 122, Update the
profile-required detection in the Kiro error mapping to only apply when status
is undefined or 400, preserving event-stream exceptions while excluding
rate-limit responses. Replace the independent evidence checks with a bounded
phrase matching profileArn followed by required, and add negative tests covering
a 429 response and an unrelated required field.

Comment thread src/oauth/kiro.ts
Comment on lines +285 to +290
if (identity.profileArn !== undefined) {
const current = readKiroCliSqliteCredential();
const importedKey = imported.refresh || imported.access;
const currentKey = current ? current.refresh || current.access : "";
if (!current || currentKey !== importedKey) identity = {};
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Revalidate every whoami identity field.

Line 285 revalidates the SQLite session only when identity.profileArn exists. If session A is imported, the CLI switches to session B, and whoami returns only B's email, Line 289 does not run. Line 306 then returns B's email with A's credentials and any authoritative SQLite profile metadata.

Run the token comparison when either identity.email or identity.profileArn exists. Add a regression test where the session changes and whoami returns an email without a valid profile ARN.

Proposed fix
-    if (identity.profileArn !== undefined) {
+    if (identity.email !== undefined || identity.profileArn !== undefined) {
       const current = readKiroCliSqliteCredential();
       const importedKey = imported.refresh || imported.access;
       const currentKey = current ? current.refresh || current.access : "";
       if (!current || currentKey !== importedKey) identity = {};
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (identity.profileArn !== undefined) {
const current = readKiroCliSqliteCredential();
const importedKey = imported.refresh || imported.access;
const currentKey = current ? current.refresh || current.access : "";
if (!current || currentKey !== importedKey) identity = {};
}
if (identity.email !== undefined || identity.profileArn !== undefined) {
const current = readKiroCliSqliteCredential();
const importedKey = imported.refresh || imported.access;
const currentKey = current ? current.refresh || current.access : "";
if (!current || currentKey !== importedKey) identity = {};
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/oauth/kiro.ts` around lines 285 - 290, Update the identity revalidation
guard in the flow around readKiroCliSqliteCredential so token comparison runs
when either identity.email or identity.profileArn is present, not only when
profileArn exists. Preserve clearing identity when the imported and current
credentials differ, and add a regression test covering a session switch where
whoami returns an email without a valid profile ARN.

@lidge-jun
lidge-jun merged commit dddc674 into dev Aug 5, 2026
36 of 38 checks passed
@lidge-jun
lidge-jun deleted the codex/lane-10-kiro-profile-arn branch August 5, 2026 09:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Kiro provider profileArn required 400 for Builder ID accounts on gated models

1 participant