Skip to content

Fix runtime skip for deactivated accounts#463

Merged
ndycode merged 8 commits into
mainfrom
fix/runtime-skip-deactivated-accounts
May 1, 2026
Merged

Fix runtime skip for deactivated accounts#463
ndycode merged 8 commits into
mainfrom
fix/runtime-skip-deactivated-accounts

Conversation

@ndycode
Copy link
Copy Markdown
Owner

@ndycode ndycode commented May 1, 2026

Summary:

  • Treat upstream 402 deactivated_workspace as a disabled workspace signal while preserving existing 403 workspace-disabled handling.
  • Disable deactivated runtime pool entries with enabled=false, clear session affinity for the request, persist the pool, and retry on another account.
  • Keep normal transient retry budgets capped while allowing terminal deactivated skips to walk the full account count.
  • Silence Windows taskkill cleanup output in TUI-facing flows without exposing process output.
  • Harden concurrent deactivation handling so one account deactivation does not double-record account failure.
  • Stabilize the index retry stress test by removing unnecessary fake-timer scheduling from a path whose retry sleeps are already mocked.

What changed:

  • Runtime proxy now detects 402 deactivated_workspace before generic error forwarding and retries the request on another enabled account.
  • Deactivated accounts are persisted through existing enabled=false storage and skipped after reload.
  • Session affinity is cleared when the pinned account is disabled so later requests can bind to a live account.
  • Pool exhaustion now returns structured codex_runtime_rotation_pool_exhausted JSON with reason: "deactivated" when all usable accounts are deactivated.
  • Unrelated 402 and 403 responses still pass through unchanged.
  • Windows process cleanup uses cmd.exe /d /s /c taskkill ... with ignored stdio to avoid noisy SUCCESS: The process with PID ... has been terminated. output.
  • Script JSDoc annotations keep typecheck:scripts green.

Verification:

  • npm.cmd test -- test/runtime-rotation-proxy.test.ts test/fetch-helpers.test.ts test/auto-update-checker.test.ts test/test-model-matrix-script.test.ts
  • npm.cmd test -- test/codex-routing.test.ts test/package-bin.test.ts
  • npm.cmd test -- test/index-retry.test.ts
  • npm.cmd run lint
  • npm.cmd run typecheck
  • npm.cmd run typecheck:scripts
  • npm.cmd run build
  • npm.cmd test
  • npm.cmd run pack:check

Stress notes:

  • Runtime/helper stress loop previously passed 20/20 after stabilizing the refresh persistence test.
  • Full npm.cmd test passed with 257 files and 3872 tests.
  • The previous concurrent npm.cmd test + npm.cmd run pack:check timeout was fixed in test/index-retry.test.ts; the concurrent stress condition now passes.

Risk:

  • Medium. The runtime retry path affects live Responses traffic, session affinity, and account persistence. Coverage now includes 402 failover, 403 workspace-disabled failover, unrelated 402/403 passthrough, all-deactivated exhaustion, mixed transient/deactivated exhaustion, persistence after reload, and concurrent deactivation failure de-duplication.

Rollback:

  • Revert the PR commits on fix/runtime-skip-deactivated-accounts.
  • Re-enable any accounts intentionally disabled during manual testing with the existing account enable flow if needed.

Docs:

  • No user-facing command or storage schema change. Existing status output already shows disabled accounts.

note: greptile review for oc-chatgpt-multi-auth. cite files like lib/foo.ts:123. confirm regression tests + windows concurrency/token redaction coverage.

Greptile Summary

this pr adds 402 deactivated_workspace failover to the runtime rotation proxy: deactivated accounts are disabled, session affinity is cleared, and the proxy retries on the remaining pool, with pool exhaustion emitting a structured codex_runtime_rotation_pool_exhausted response. the transient-vs-deactivated attempt tracking and the post-loop exhaustion reason promotion are well-designed and the concurrent deactivation guard (accountWasEnabled) correctly prevents double-recording.

Confidence Score: 5/5

safe to merge — all new paths are guarded, tested, and have no P0/P1 issues

no P0 or P1 issues found; concurrency guard, exhaustion reason promotion, and transient-vs-deactivated budget split are all correct; coverage now includes 402 failover, 403 failover, concurrent deactivation, mixed exhaustion, persistence, and passthrough — only minor P2 style observations remain

lib/runtime-rotation-proxy.ts and lib/request/fetch-helpers.ts are the highest-impact files but reviewed clean

Important Files Changed

Filename Overview
lib/runtime-rotation-proxy.ts core change: 402/403 workspace-disabled detection, deactivated-account skip path, transientAttempts budget split, and post-loop exhaustion reason promotion — logic is sound and well-guarded against concurrent deactivation
lib/request/fetch-helpers.ts extends isWorkspaceDisabledError to handle 402 status with deactivated_workspace token or body-text regex; 403 handling is preserved unchanged
test/runtime-rotation-proxy.test.ts adds 8 new proxy-level tests covering 402 failover, 403 workspace-disabled failover, concurrent deactivation de-duplication, all-deactivated exhaustion, mixed transient+deactivated exhaustion, unrelated 402/403 passthrough, and persistence after reload
test/fetch-helpers.test.ts adds unit tests for the new 402 deactivated_workspace path and verifies the false-positive guard for payment_required and plain body text
lib/auto-update-checker.ts wraps taskkill in cmd.exe /d /s /c to silence the SUCCESS stdout noise on windows; no logic change
scripts/test-model-matrix.js extracts runQuietWindowsTaskkill using the same cmd.exe /d /s /c pattern; consistent with the auto-update-checker change
test/index-retry.test.ts removes vi.useFakeTimers() from a test that doesn't need fake timer control, eliminating the source of flakiness under concurrent load
scripts/codex-multi-auth.js adds jsdoc annotation to normalizeStandaloneArgs to keep typecheck:scripts green; no logic change
scripts/codex-routing.js adds jsdoc annotations to normalizeAuthAlias and shouldHandleMultiAuthAuth; no logic change

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Incoming request] --> B{chooseAccount}
    B -- no account --> Z[break: pool exhausted]
    B -- account selected --> C[consumeToken]
    C --> D[ensureFreshAccessToken]
    D -- not ok retryable --> E[transientAttempts++
exhaustionReason=auth-failure]
    D -- not ok non-retryable --> E2[exhaustionReason=auth-failure
continue]
    D -- ok --> F[fetch upstream]
    F -- network error --> G[transientAttempts++
exhaustionReason=network-error
continue]
    F -- rate-limit 429 --> H[transientAttempts++
exhaustionReason=rate-limit
continue]
    F -- 402 or 403 --> I{isWorkspaceDisabledError?}
    I -- yes --> J[refundToken
recordFailure if accountWasEnabled
setAccountEnabled false
forgetSession
exhaustionReason=deactivated
continue]
    I -- no --> K[forward bodyText to client
record usage
return]
    F -- 401 --> L[transientAttempts++
exhaustionReason=auth-failure
continue]
    F -- 5xx --> M[transientAttempts++
exhaustionReason=server-error
continue]
    F -- 200 OK --> N[stream forward to client
return]
    E --> LOOP{while:
attemptedIndexes lt accountCount
AND transientAttempts lt transientAttemptLimit}
    J --> LOOP
    G --> LOOP
    H --> LOOP
    L --> LOOP
    M --> LOOP
    E2 --> LOOP
    LOOP -- continue --> B
    LOOP -- exit --> P{post-loop reason}
    P -- transientAttempts gte limit AND size lt count --> Q[exhaustionReason=budget]
    P -- exhaustionReason==deactivated AND transientReason set --> R[exhaustionReason=transientExhaustionReason]
    P --> S[writePoolExhausted]
    Z --> S
Loading

Fix All in Codex

Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
lib/request/fetch-helpers.ts:338-341
**402 body-text fallback matches `deactivated_workspace` in any field**

the bodyText regex `/\bdeactivated_workspace\b/i` fires on any occurrence of that string in the raw body — including inside an `error.message` like `"contact support: deactivated_workspace"`. `extractErrorCodeFromBody` already handles structured JSON and returns the code, so the regex is only a fallback for unstructured responses. in practice the risk is low, but worth noting that a `{"error": {"message": "deactivated_workspace info"}}` body with no `code` field would still trigger account disable on 402.

### Issue 2 of 2
lib/runtime-rotation-proxy.ts:1113-1121
**non-workspace 402/403 forwarding skips `Content-Length` recalculation**

`bodyText` is forwarded with the original upstream headers (including any `content-length` set by the upstream). since `readErrorBody` decodes via `.text()`, a body with a `content-encoding` or multi-byte encoding could produce a string whose `Buffer.byteLength` differs from the original header value, sending a mismatched `Content-Length` to the client. in practice 402/403 error bodies are always UTF-8 JSON so this is unlikely, but if upstream ever gzip-encodes an error response the forwarded bytes will mismatch.

Reviews (5): Last reviewed commit: "stabilize index retry stress test" | Re-trigger Greptile

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 1, 2026

📝 Walkthrough

Walkthrough

adds http 402 support for deactivated workspace detection. isWorkspaceDisabledError now normalizes error codes once and checks 402 status with deactivated_workspace matching. runtime-rotation-proxy extracts error codes from response bodies, disables accounts on deactivation, tracks transient attempts separately, and marks exhaustion as "deactivated" when applicable.

Changes

Cohort / File(s) Summary
Core Deactivation Logic
lib/request/fetch-helpers.ts, lib/runtime-rotation-proxy.ts
fetch-helpers now normalizes code once and adds dedicated 402 handler for deactivated_workspace detection via exact match, token presence, or regex. runtime-rotation-proxy introduces extractErrorCodeFromBody for robust json parsing, adds "deactivated" exhaustion reason, refactors retry loop to track distinct account attempts vs. capped transient failures separately, and routes 402/403 responses through workspace-disabled detection to disable accounts and continue rotation.
Fetch-Helpers Tests
test/fetch-helpers.test.ts
adds coverage for 402 status with deactivated_workspace token in error code or json body, verifies 402 alone is insufficient without recognized disabled signal, and tests that free-text "disabled" messages without token return false.
Runtime Rotation Tests
test/runtime-rotation-proxy.test.ts
introduces 9 new tests covering: deactivation with 402/403 upstream responses, account disabling and retry with other enabled accounts, session affinity clearing, storage persistence across restarts, pool exhaustion returning service-unavailable with code/reason, transient failure reporting on mixed deactivation/transient scenarios, and forwarding unrelated 402 codes without account disabling.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Proxy as Runtime Rotation Proxy
    participant Upstream
    
    Client->>Proxy: Request (account selected)
    Proxy->>Upstream: Forward request
    Upstream-->>Proxy: HTTP 402/403 + error body
    
    Proxy->>Proxy: extractErrorCodeFromBody()<br/>parse code field
    Proxy->>Proxy: isWorkspaceDisabledError()<br/>check deactivated_workspace
    
    alt Deactivated Workspace Detected
        Proxy->>Proxy: Disable account
        Proxy->>Proxy: Clear session affinity
        Proxy->>Proxy: Increment rotation counter
        Proxy->>Proxy: Mark exhaustion reason
        Proxy->>Proxy: Select next enabled account
        Proxy->>Upstream: Retry request (different account)
    else Unrelated Error
        Proxy-->>Client: Forward 402/403 to client
        Proxy->>Proxy: Record as failure
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

review flags:

  • lib/runtime-rotation-proxy.ts: exhaustion reason logic around line 85+ needs careful tracing—the interaction between transient attempt capping and deactivation substitution is intricate. verify that final exhaustion reason computation correctly prioritizes "budget" over deactivated reason when both conditions occur.
  • missing regression coverage: no tests verify deactivation behavior under concurrent requests to the same workspace. ensure session affinity clearing doesn't race with in-flight requests using the disabled account.
  • windows edge case: extractErrorCodeFromBody parses json from response bodies without explicit charset handling. confirm error bodies from upstream services don't arrive with windows-1252 or other encodings that could corrupt code field extraction.
  • concurrency risk: disabling accounts via selectedAccount.disabled = true (implied from "disable selected account") must be atomic relative to other rotation threads. if multiple requests hit rotation simultaneously with the same account, ensure double-disable or concurrent disable/select doesn't occur.
  • test/runtime-rotation-proxy.test.ts: all new tests cover happy path deactivation flow. add negative test: account disabled mid-request while another request reads account state.

Suggested labels

bug

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title follows conventional commits format (fix: summary) with a lowercase imperative summary under 72 characters that accurately reflects the main change.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed pr description is comprehensive: clear summary of changes, detailed 'what changed' section, thorough verification steps, and risk/rollback analysis provided. all required template sections present with substantive content.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 fix/runtime-skip-deactivated-accounts
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch fix/runtime-skip-deactivated-accounts

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.

❤️ Share

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

@chatgpt-codex-connector
Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Credits must be used to enable repository wide code reviews.

Comment thread lib/runtime-rotation-proxy.ts
Comment thread lib/runtime-rotation-proxy.ts
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@test/runtime-rotation-proxy.test.ts`:
- Around line 831-941: Add a deterministic vitest that covers the 403
passthrough regression: create an AccountManager (e.g., new
AccountManager(undefined, createStorage(now, 2))) and a createRecordingFetch
implementation that returns a 403 JSON error (non-workspace_disabled) on the
first attempt; start the proxy with startProxy({ accountManager, fetchImpl });
POST via postResponses and assert the proxied response status/body are forwarded
unchanged, that only one upstream call was made (calls.length === 1), and that
accountManager.getAccountByIndex(0)?.enabled and getAccountByIndex(1)?.enabled
remain true; place the test near the other rotation tests (around the block
covering 922-941) to exercise the branch in lib/runtime-rotation-proxy.ts (the
disable-or-forward logic at ~1085-1117) and ensure determinism with vitest
utilities already used in the file.
🪄 Autofix (Beta)

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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 265fbfb5-c3c5-46bd-a865-dc92b34140f8

📥 Commits

Reviewing files that changed from the base of the PR and between 3f335e4 and be6fc7d.

📒 Files selected for processing (4)
  • lib/request/fetch-helpers.ts
  • lib/runtime-rotation-proxy.ts
  • test/fetch-helpers.test.ts
  • test/runtime-rotation-proxy.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Greptile Review
🧰 Additional context used
📓 Path-based instructions (2)
test/**

⚙️ CodeRabbit configuration file

tests must stay deterministic and use vitest. demand regression cases that reproduce concurrency bugs, token refresh races, and windows filesystem behavior. reject changes that mock real secrets or skip assertions.

Files:

  • test/fetch-helpers.test.ts
  • test/runtime-rotation-proxy.test.ts
lib/**

⚙️ CodeRabbit configuration file

focus on auth rotation, windows filesystem IO, and concurrency. verify every change cites affected tests (vitest) and that new queues handle EBUSY/429 scenarios. check for logging that leaks tokens or emails.

Files:

  • lib/runtime-rotation-proxy.ts
  • lib/request/fetch-helpers.ts
🔇 Additional comments (3)
lib/request/fetch-helpers.ts (1)

331-344: 402 deactivated workspace detection looks correct.

the new 402 branch in lib/request/fetch-helpers.ts:331-344 cleanly matches deactivated_workspace via normalized code, tokenized code, and body text, and keeps 403 logic intact. coverage is present in test/fetch-helpers.test.ts:691-706.

test/fetch-helpers.test.ts (1)

691-706: good regression coverage for 402 token-gated classification.

the cases in test/fetch-helpers.test.ts:691-706 correctly prove that 402 alone is not enough and that deactivated_workspace is required, matching lib/request/fetch-helpers.ts:338-344.

lib/runtime-rotation-proxy.ts (1)

948-959: the transient-budget vs deactivated-skip split is implemented correctly.

the loop/accounting updates in lib/runtime-rotation-proxy.ts:948-959 and lib/runtime-rotation-proxy.ts:1213-1224, plus the disable+retry branch in lib/runtime-rotation-proxy.ts:1085-1117, match the intended behavior for mixed transient and deactivated pools.

Also applies to: 1085-1117, 1213-1224

Comment thread test/runtime-rotation-proxy.test.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant