fix(kiro): do not permanently ban on 'User is not authorized to make this call' (#11809)

* fix(kiro): do not permanently ban on 'User is not authorized to make this call'

* test(kiro): regression cover the 403 'User is not authorized' non-ban classification

---------

Co-authored-by: Deftera186 <Deftera186@users.noreply.github.com>
Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
This commit is contained in:
Deftera
2026-09-02 06:02:31 +03:00
committed by GitHub
parent 5a0a131bc7
commit 678e6077e4
2 changed files with 70 additions and 0 deletions

View File

@@ -363,6 +363,17 @@ export function classifyProviderError(
if (recoverableProject403) {
return PROVIDER_ERROR_TYPES.PROJECT_ROUTE_ERROR;
}
// Kiro IDC missing profileArn — AWS returns 403 "User is not authorized to make this call"
// when the request is sent without a profileArn or to the wrong Q Developer region.
// This is a recoverable configuration issue, not a ban: the account still works in Kiro IDE.
// Do NOT classify as FORBIDDEN (which bans permanently). Treat as PROJECT_ROUTE_ERROR
// so the connection stays active and can be retried after profile discovery (#10725).
const isKiroProfile403 =
(p === "kiro" || p === "amazon-q") &&
bodyStr.includes("User is not authorized to make this call");
if (isKiroProfile403) {
return PROVIDER_ERROR_TYPES.PROJECT_ROUTE_ERROR;
}
// A Cloudflare Sentinel/Turnstile 403 is a TERMINAL block for browser-session
// providers: the user's IP/session needs a browser Turnstile challenge, and
// retrying the same connection will keep 403ing. Classify as FORBIDDEN so

View File

@@ -0,0 +1,59 @@
import test from "node:test";
import assert from "node:assert/strict";
import {
classifyProviderError,
PROVIDER_ERROR_TYPES,
} from "../../open-sse/services/errorClassifier.ts";
// #11809 (follow-up to #10725) — a Kiro/Amazon Q IdC account whose Identity Center
// lives outside the Q Developer profile regions is stored without a profileArn, so
// CodeWhisperer answers 403 "User is not authorized to make this call". That is a
// RECOVERABLE configuration issue (the same token succeeds once the profile ARN is
// discovered, and the account keeps working in Kiro IDE) — not a ban. Before the fix
// it fell through to FORBIDDEN, which markAccountUnavailable turns into the terminal
// "banned" state (is_active=0) and required a full re-auth on every authentication.
const KIRO_MISSING_ARN_403 = {
message: "User is not authorized to make this call",
};
test("#11809: kiro 403 'User is not authorized to make this call' -> PROJECT_ROUTE_ERROR, not FORBIDDEN", () => {
assert.equal(
classifyProviderError(403, KIRO_MISSING_ARN_403, "kiro"),
PROVIDER_ERROR_TYPES.PROJECT_ROUTE_ERROR,
);
});
test("#11809: amazon-q shares the Kiro executor/credentials -> same recoverable classification", () => {
assert.equal(
classifyProviderError(403, KIRO_MISSING_ARN_403, "amazon-q"),
PROVIDER_ERROR_TYPES.PROJECT_ROUTE_ERROR,
);
});
test("#11809: the message is matched inside a raw CodeWhisperer error body too", () => {
const body =
'{"__type":"AccessDeniedException","message":"User is not authorized to make this call."}';
assert.equal(
classifyProviderError(403, body, "kiro"),
PROVIDER_ERROR_TYPES.PROJECT_ROUTE_ERROR,
);
});
test("control: an unrelated kiro 403 still bans (FORBIDDEN) — carve-out is message-scoped", () => {
assert.equal(classifyProviderError(403, "Forbidden", "kiro"), PROVIDER_ERROR_TYPES.FORBIDDEN);
});
test("control: the same message on a non-Kiro oauth provider keeps FORBIDDEN — carve-out is provider-scoped", () => {
assert.equal(
classifyProviderError(403, KIRO_MISSING_ARN_403, "claude"),
PROVIDER_ERROR_TYPES.FORBIDDEN,
);
});
test("control: a real Kiro ban signal still classifies as ACCOUNT_DEACTIVATED", () => {
assert.equal(
classifyProviderError(403, "your account has been suspended", "kiro"),
PROVIDER_ERROR_TYPES.ACCOUNT_DEACTIVATED,
);
});