Repository navigation
fix(kiro): fail over accounts when overage quota is exhausted - #7687
Conversation
|
No actionable comments were generated in the recent review.
|
| Layer / File(s) | Summary |
|---|---|
Classify overage-limit refusals src/adapters/kiro-refusal.ts, tests/providers/kiro/kiro-refusal.test.ts |
The classifier returns monthly_quota for the exact OVERAGE_REQUEST_LIMIT_EXCEEDED reason on HTTP 400 or 429. Tests check other statuses, unrelated or nested evidence, suffixed reasons, and oversized bodies. |
Verify account rotation and document quota reasons tests/server/server-kiro-refusal-e2e.test.ts, docs-site/src/content/docs/reference/adapters.md, docs-site/src/content/docs/reference/cli/providers-accounts.md, structure/providers/kiro.md |
Buffered and streaming tests cover rotation after monthly and overage refusals. They verify that account B serves the response and becomes active. The documentation lists the exact refusal reasons; the CLI reference also notes that overage-enabled plans may continue serving at a monthly gauge of 100%. |
Priority:
Estimated code review effort: 2 (Simple) | ~10 minutes
Change: Bug fix
Sequence Diagram(s)
sequenceDiagram
participant Kiro as Kiro upstream
participant Classifier as classifyKiroRefusal
participant Rotation as Account rotation
participant AccountB as Account B
Kiro->>Classifier: HTTP 400 or 429 with overage-limit reason
Classifier->>Rotation: monthly_quota verdict
Rotation->>Kiro: Retry using Account B credentials
Kiro->>Rotation: Successful response
Rotation->>AccountB: Set active account
Possibly related issues
- [Design] Minimum safe Desktop composer compatibility across client updates #7587 — This change adds Kiro overage-limit classification to account rotation. The broader Desktop compatibility requirements remain unimplemented.
Possibly related PRs
- lidge-jun/opencodex#7625 — Both changes enable quota-related failover. This PR classifies Kiro refusals; PR
#7625changes combo hop and cooldown classification. - lidge-jun/opencodex#7558 — Both changes handle provider-specific HTTP 400 quota or credit refusals for account rotation. This PR addresses Kiro; PR
#7558addresses Command Code.
Suggested labels: bug, review-ready
Merge Risk: 
Minimal · up to 87d9b
-
The change is ready to merge after the outstanding required CI checks complete successfully.
Pre-merge checks: 
5
-
Full details: Title check
-
Explanation
The title "fix(kiro): fail over accounts when overage quota is exhausted" matches the main change. The PR adds OVERAGE_REQUEST_LIMIT_EXCEEDED on HTTP 400 or 429 to the existing monthly_quota classification in src/adapters/kiro-refusal.ts, so an exhausted account is excluded and the request fails over to another account. The title is specific and concise, and it follows the conventional commit style. The raw summary does not show any unrelated file changes, and the docs and tests all support the same behavior change.
-
Finishing Touches
-
🧪 Generate unit tests (beta)
-
- Create a new PR
-
- Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.
Comment @coderabbitai help to get the list of available commands.
|
✅ Deterministic PR hygiene checks passed. |
✅ READY
Review readiness checklist
✅ 4/4 boxes ticked. This pull request is already Ready for Review. |
Local CI-equivalent (macOS) at 87d9bb9FAIL — owner-approved real-machine rule; full primary Bun batch/file coverage is complete. Raw nonzero exits remain failures; no retry is counted as recovery.
Every primary batch below uses this exact command with its ordered timeout --signal=TERM --kill-after=15s 300s bun test --isolate --parallel=1 --timeout 60000 "${FILES[@]}"Failure classification:
Observed failing assertion: Blocking items: 3of3-batch-017-of-81 Every primary batch: exit and pass/fail/skip
Setup, CLI smoke, and macOS-specific gates:
Exact setup, smoke and gate commands (same order as the table)identity ( git rev-parse HEAD; git merge-base HEAD 26598df2c4; bun --version; bun --revision; node --version; sw_vers; hostname; uname -msetup-test-runner ( node <<'NODE'
const pkg = require('./package.json');
const role = process.env.BUN_ROLE;
let key;
let version;
switch (role) {
case 'runtime': key = 'dependencies.bun'; version = pkg.dependencies?.bun; break;
case 'test-runner': key = 'testRunnerBun'; version = pkg.testRunnerBun; break;
default:
console.error(`::error::Unknown Bun role: ${role}`);
process.exit(1);
}
if (typeof version !== 'string' || version !== version.trim() || !/^\d+\.\d+\.\d+$/.test(version)) {
console.error(`::error::Missing or invalid package.json ${key} for Bun role ${role}`);
process.exit(1);
}
require('node:fs').appendFileSync(process.env.GITHUB_OUTPUT, `version=${version}\n`);
console.log(`Using Bun ${version} from package.json ${key} (${role})`);
NODE
bun --version
test "$(bun --version)" = 1.4.3install-root ( bun install --frozen-lockfileinstall-gui ( cd gui && bun install --frozen-lockfilebuild-gui ( cd gui && bun run buildbounded-utilities ( brew list coreutils >/dev/null 2>&1 || brew install coreutils
command -v timeout
timeout --versionhelp-1of3 ( bun run src/cli/index.ts helphelp-2of3 ( bun run src/cli/index.ts helphelp-3of3 ( bun run src/cli/index.ts helphelp-1of1 ( bun run src/cli/index.ts helpwidget-setup-bun-runtime ( node <<'NODE'
const pkg = require('./package.json');
const role = process.env.BUN_ROLE;
let key;
let version;
switch (role) {
case 'runtime': key = 'dependencies.bun'; version = pkg.dependencies?.bun; break;
case 'test-runner': key = 'testRunnerBun'; version = pkg.testRunnerBun; break;
default:
console.error(`::error::Unknown Bun role: ${role}`);
process.exit(1);
}
if (typeof version !== 'string' || version !== version.trim() || !/^\d+\.\d+\.\d+$/.test(version)) {
console.error(`::error::Missing or invalid package.json ${key} for Bun role ${role}`);
process.exit(1);
}
require('node:fs').appendFileSync(process.env.GITHUB_OUTPUT, `version=${version}\n`);
console.log(`Using Bun ${version} from package.json ${key} (${role})`);
NODE
bun --version
test "$(bun --version)" = 1.4.4widget-01 ( bun install --frozen-lockfile
cd desktop
bun install --frozen-lockfile
widget-setup-rust ( rustup toolchain install stable --profile minimal
rustc --version
cargo --versionwidget-02 ( bun run test:macos
swift run --package-path app NativeTrayTests
widget-03 ( bun run build:guiwidget-04 ( bun desktop/scripts/prepare-sidecar.tswidget-05 ( bash desktop/scripts/build-widget.shwidget-06 ( bunx tauri build --ci --bundles app --config '{"bundle":{"createUpdaterArtifacts":false,"macOS":{"signingIdentity":"-"}}}'widget-07 ( app=desktop/src-tauri/target/release/bundle/macos/OpenCodex.app
# Tauri renames the main binary only when `mainBinaryName` is set, and this config
# does not set it, so the bundled executable keeps the Cargo bin name rather than
# the product name. Read the name the bundle itself declares instead of restating
# it here, so this check follows the config instead of drifting from it.
executable="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleExecutable' "$app/Contents/Info.plist")"
test -n "$executable"
test -x "$app/Contents/MacOS/$executable"
test -x "$app/Contents/PlugIns/OpenCodexWidget.appex/Contents/MacOS/OpenCodexWidget"
test -x "$app/Contents/MacOS/ocx"
# Verify actual entitlements and execute the signed Bun sidecar as well as the seal.
bash desktop/scripts/verify-macos-runtime.sh "$app"
codesign -dv "$app/Contents/PlugIns/OpenCodexWidget.appex"
# The widget is only offered in the gallery when its bundle is actually linked in, and
# nothing else here would notice its absence: the appex builds, signs and registers
# exactly the same way with the WidgetBundle dropped by the linker.
nm -a "$app/Contents/PlugIns/OpenCodexWidget.appex/Contents/MacOS/OpenCodexWidget" \
| grep -q "OpenCodexWidget0abC6BundleV" \
|| { echo "::error::the widget bundle is not linked into the extension"; exit 1; }
widget-post-probe-first-attempt ( app=desktop/src-tauri/target/release/bundle/macos/OpenCodex.app
codesign -dv "$app/Contents/PlugIns/OpenCodexWidget.appex"
# The widget is only offered in the gallery when its bundle is actually linked in, and
# nothing else here would notice its absence: the appex builds, signs and registers
# exactly the same way with the WidgetBundle dropped by the linker.
nm -a "$app/Contents/PlugIns/OpenCodexWidget.appex/Contents/MacOS/OpenCodexWidget" \
| grep -q "OpenCodexWidget0abC6BundleV" \
|| { echo "::error::the widget bundle is not linked into the extension"; exit 1; }
final-state ( git rev-parse HEAD; git status --porcelainNOT RUN:
|
Addendum: CLI child crash diagnostics at 87d9bb9The original FAIL remains recorded. Diagnostic repetitions do not replace it.
Exact original shard3/3 batch017 command, same ordered files for HEAD×3 and BASE×3: timeout --signal=TERM --kill-after=15s 300s bun test --isolate --parallel=1 --timeout 60000 tests/cli/cli-models-reasoning.test.ts tests/cli/cli-models-set.test.ts tests/cli/cli-provider-lifecycle-runtime.test.ts tests/cli/cli-provider-list-active.test.ts tests/cli/cli-provider-settings.test.ts tests/cli/cli-restart-handoff.test.ts tests/cli/cli-restart-health.test.ts tests/cli/cli-start-journal-order.test.ts tests/cli/cli-status-json.test.ts tests/cli/cli-system-settings-parity.test.ts tests/cli/cli-transport-honesty.test.ts tests/cli/cli-update-restart-admission.test.tsHEAD file-alone×3: timeout --signal=TERM --kill-after=15s 300s bun test --isolate --parallel=1 --timeout 60000 tests/cli/cli-status-json.test.tsOriginal batch env preserved: Original failure (batch362 pass/1 fail, exit1; same head control batch050 passed302/0/0): Failing assertion text in these nine runs: none. Assessment: the original child crash is intermittent/non-reproducible in this sample: exact batch order passed3/3 at HEAD and3/3 on base, and HEAD alone passed3/3. Fixed batch order alone did not reproduce it. The precise cause, dependence on earlier suite state, and deterministic source regression remain unproven. The original comparison classification and overall FAIL remain unchanged. All nine requested runs completed; NOT RUN: none of the requested diagnostic runs. Commands/env/full logs: |
|
Integration evidence (owner decision decision:e6895606, approved): independent gpt-6.1-sol review PASS at 87d9bb9. Exact-head PR CI run 40102495440 succeeded (four Ubuntu test shards, gates, aggregate ci). Not Windows-sensitive. A real-machine macOS run on this head recorded one failure in tests/cli/cli-status-json.test.ts:132, a Bun 1.4.3 runtime trap in a CLI child unrelated to this diff; the same head's control batch and dev both passed that file. Static merge simulation on current dev f0bf610 (this Mac): dev=f0bf610bee7be9eae595dc4b1334727462a9b1a2 base=0aeaf0cb6b0a7c2f117d8c75f8fa396cf2ea6e39 pr_files= 6 overlap=[]; merge clean; install exit=0; gui install exit=0; tsc exit=0; tsc doctor exit=0; structure:check exit=0; privacy:scan exit=0; layout+ratchet guards exit=0 52 pass 0 fail ; Merged by the triage manager as maintainer integration (MAINTAINERS.md, dev only) with green exact-head PR CI, an independent review PASS, and the owner's approval of this bundle. |
Summary
OVERAGE_REQUEST_LIMIT_EXCEEDEDreason on HTTP 400 or 429 as account quota exhaustion, using the existing monthly-quota recovery path. A pool with other eligible accounts could previously return an HTTP 400 to the client without attempting another account. The HTTP 429 variant rotated but retained only a short rate cooldown.Reproduction
Store two usable Kiro OAuth accounts, select the first, and return this synthetic upstream body for its first generation request:
{ "__type": "com.amazon.kiro.runtimeservice#ServiceQuotaExceededException", "reason": "OVERAGE_REQUEST_LIMIT_EXCEEDED", "message": "You have reached the limit for overages." }Before the fix, HTTP 400 returned to the caller without using the second account, and HTTP 429 failed to record persistent account exhaustion. With the fix, both response modes retry exactly once on the second account, carry that account's bearer/profile/region, update the active selection, and mark only the refused account exhausted. The regression was driven red before changing the classifier.
Verification
Runtime verification below was completed on
a383e412ea2588223848d4e7ba74dbce2c2f5850, based on upstreamdevat0aeaf0cb6b0a7c2f117d8c75f8fa396cf2ea6e39. Current head87d9bb9274706746ec99547abea98cd274890001adds only the classifier docstring requested by CodeRabbit; its focused revalidation is recorded below.bun scripts/test.ts tests/providers/kiro/kiro-refusal.test.ts tests/server/server-kiro-refusal-e2e.test.ts tests/providers/kiro/kiro-refusal-failover.test.ts tests/providers/kiro/kiro-leased-responses.test.ts tests/providers/kiro/kiro-auth-context-continuation.test.ts tests/oauth/generic-oauth-refusal-composition.test.ts— 84 passed, 0 failed.bun run typecheck— passed.bun run privacy:scan— passed.bun run structure:check— passed.docs-site/,bun install --frozen-lockfileandbun run build— passed; 593 pages built and 84,861 internal links checked.git diff --check— passed before committing.bun run test:changedresolved upstreamdevto0aeaf0cb6b0a7c2f117d8c75f8fa396cf2ea6e39— 34,418 passed, 43 skipped, 0 failed, across 1,547 files (310.1 seconds).Full-suite scope exception (AGENTS.md, Commands): this change adds one exact reason to an existing bounded classifier, while the full repository has 2,607 test files. The focused tests exercise classification, actual Responses account rotation in both response modes, persisted account exclusion, paired credentials and routing metadata, leases, continuation, cancellation, and refusal recovery. The broader import-graph run also passed as recorded above. The complete 2,607-file suite was not run locally; upstream CI remains required before merge. No runtime changes outside the classifier, configuration changes, or production deployment are included.
CodeRabbit pre-merge follow-up (2026-10-24)
87d9bb9274706746ec99547abea98cd274890001contains only that documentation follow-up. With repository-pinned Bun 1.4.3,bun scripts/test.ts tests/providers/kiro/kiro-refusal.test.ts tests/server/server-kiro-refusal-e2e.test.ts— 45 passed, 0 failed;bun run typecheckandgit diff --checkalso passed.Readiness was reattested after the gate processed the docstring-only push and reset the checklist. The clean working tree and PR still match
87d9bb9274706746ec99547abea98cd274890001; the branch is 0 commits behind currentdev. The recorded current-head 45-test run and typecheck apply unchanged, and the full-suite scope exception remains documented above.Checklist
Review readiness checklist
This PR stays in draft until every box below is ticked. Tick all four boxes once the requirements are met:
Required local validation passed; commands, results, and any full-suite exception are documented.
I pushed my PR to a recent dev commit (at most 10 behind; a maintainer may still ask for the exact tip before merge).
I resolved all correct Codex and CodeRabbit findings.
My PR is ready for review.
Summary by CodeRabbit
Bug Fixes
OVERAGE_REQUEST_LIMIT_EXCEEDED, likeMONTHLY_REQUEST_COUNT, as monthly quota exhaustion when returned with HTTP 400 or 429.Documentation