Skip to content

fix(anthropic): rotate past an account whose organization disabled subscription access - #7175

Closed
vadymhimself wants to merge 1 commit into
lidge-jun:devfrom
vadymhimself:fix/anthropic-org-disabled-rotation
Closed

vadymhimself wants to merge 1 commit into
lidge-jun:devfrom
vadymhimself:fix/anthropic-org-disabled-rotation

Conversation

@vadymhimself

@vadymhimself vadymhimself commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • When an OAuth pool account belongs to an organization that has disabled Claude subscription access, Anthropic answers HTTP 403 with oauth_org_not_allowed and "Your organization has disabled Claude subscription access for Claude Code. Use an Anthropic API key instead, or ask your admin to enable access." isAccountRefusal in src/oauth/anthropic-account-refusal.ts did not recognise that refusal. As a result the denied account was never cooled. It stayed eligible, and every turn routed to it failed with 403 even when healthy pool accounts were available. On combos, each such turn also cost a hop and a target cooldown.
  • That 403 is now classified as an account refusal. The structured oauth_org_not_allowed value is matched first, as either error.type or error.code (it joins the existing allowed-code list). The whole message is also matched under permission_error, anchored the same way as its sibling entitlement messages. A prefixed or quoted copy of the message stays terminal.
  • Beyond the classifier there is no new mechanism. The existing 403 path records the process-local ACCOUNT_REFUSAL_COOLDOWN_MS cooldown (10 min, or Retry-After), clears affinity and rotates to the next eligible account. If no retry send remains, it still records the cooldown.
  • Why a cooldown and not reauthentication: the access token is still valid. A fresh login returns the same organization policy, and an admin can re-enable access at any time. This is the same reasoning the account-pool contract already applies to its sibling entitlement 403s ("Plan renewal therefore requires no permanent reauthentication flag"). A needsReauth mark would show a misleading needs-reauth state in ocx account list, and re-login could not clear the underlying cause. The cost of this choice is at most one refused send per account every 10 minutes while the organization keeps access disabled.
  • structure/providers/anthropic-account-pool.md ("Account entitlement refusal recovery") is updated to describe the accepted set.
  • This change touches src/oauth/, so it needs a maintainer-sponsored label before merge (CONTRIBUTING, pull request contract).

Verification

  • Red before the fix: with only the new test added on upstream/dev 7640a94, bun test tests/adapters/anthropic/anthropic-quota-dispatch.test.ts gave 41 pass, 3 fail. The 3 failures were the organization-disabled 403 rotates and cools the refused account (0..2) cases: permission_error + message, oauth_org_not_allowed type, and oauth_org_not_allowed code.
  • Green after the fix, same file: 44 pass, 0 fail. The new terminal row Request denied: <message> passes before and after the fix.
  • bun test --isolate tests/adapters/anthropic tests/claude-integration/messages-native-oauth.test.ts tests/claude-integration/anthropic2-native-routing.test.ts (Bun 1.4.3): 1406 pass, 0 fail.
  • bun run test:changed (Bun 1.4.3 via OCX_TEST_RUNNER_BUN): 14974 pass, 35 skip, 1 fail across 614 files.
    • The failure is tests/cli/cli-connect-readiness.test.ts, "a rejected preferred runtime falls back without rewriting the saved selection". It fails identically on a clean upstream/dev checkout (98 pass, 1 fail for that file plus github-copilot-wire-defaults), so this change did not cause it.
    • One run also timed out github-copilot-wire-defaults "grok-4.6 reaches /responses on responses inbound replay" (5 s) under load. That test passes in isolation.
  • bun run typecheck: clean.
  • bun run structure:check: passed.
  • bun run privacy:scan: passed.
  • Full bun run test was not run separately; test:changed already reached 614 files through the import graph of src/oauth/anthropic-account-refusal.ts. CI covers the rest.
  • No GUI change.
  • Maintainer review (head 554877af9, no changes pushed): bun test tests/adapters/anthropic/anthropic-quota-dispatch.test.ts 44 pass / 0 fail; bun test --isolate tests/adapters/anthropic tests/claude-integration/messages-native-oauth.test.ts tests/claude-integration/anthropic2-native-routing.test.ts 1406 pass / 0 fail; bun run typecheck, bun run structure:check, bun run privacy:scan clean. An independent review found no defects. git merge-tree against feat(accounts): auto-pause repeated permanent account refusals #7153 merges cleanly; feat(accounts): auto-pause repeated permanent account refusals #7153's classifyPermanentAccountRefusal does not yet recognize this oauth_org_not_allowed refusal, so that PR is the one to extend.

Checklist

  • Scope stays focused and avoids unrelated cleanup.
  • Docs or release notes were updated when needed.
  • Security-sensitive changes were reviewed for secrets, auth, and unsafe defaults.

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.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Organization-disabled Anthropic account errors can now trigger account rotation and temporarily cool the refused account when identified by the supported error type or code.
    • Under permission_error, matching organization-disabled subscription messages also qualify for account recovery, including messages with instructions to use an API key or ask an administrator to enable access.
    • Organization-disabled messages without qualifying error details remain terminal.
  • Documentation

    • Clarified which organization-related refusals qualify for account recovery.

@coderabbitai

coderabbitai Bot commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository: lidge-jun/opencodex/.coderabbit.yaml
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: d5067229-bc69-4f74-8d84-7f27351ef392
📥 Commits

Reviewing files that changed from the base of the PR and between 7640a94 and 554877a.

📒 Files selected for processing: 3
  • src/oauth/anthropic-account-refusal.ts
  • structure/providers/anthropic-account-pool.md
  • tests/adapters/anthropic/anthropic-quota-dispatch.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 5 remain after this review.

📝 Walkthrough

Anthropic account refusal detection now recognizes oauth_org_not_allowed and matching organization-disabled subscription messages. Documentation and quota dispatch tests cover account rotation for qualifying refusals and terminal handling for request-level 403 responses.

Changes: Anthropic refusal handling

Layer / File(s) Summary
Recognize organization-disabled refusals
src/oauth/anthropic-account-refusal.ts, tests/adapters/anthropic/anthropic-quota-dispatch.test.ts, structure/providers/anthropic-account-pool.md
The refusal matcher accepts oauth_org_not_allowed as an error type or code, and matches organization-disabled subscription wording for permission_error. Tests verify account rotation for qualifying refusals and terminal handling for the request-level message. The account-pool documentation describes the added recovery condition.

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix

Suggested reviewers: lidge-jun

Merge Risk: ⚪ Minimal · up to 55487

The change adds organization-disabled refusals to the documented cooldown-and-rotation handling, with no unresolved merge-blocking behavior identified.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 1 functions across 2 files. (1 skipped: 1 … Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
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 Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly and concisely describes the primary change: rotating away from an Anthropic account whose organization disabled subscription access. It matches the implementation and tests.
Full details: Docstring Coverage

Explanation

Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 1 functions across 2 files. (1 skipped: 1 unsupported.)

  • Fix all pre-merge checks with AI
✨ 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.

❤️ Share

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

@vadymhimself

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@lidge-jun

Copy link
Copy Markdown
Owner

Cross-reference for #7153 (auto-pause repeated permanent account refusals): the two PRs merge cleanly, but #7153's classifyPermanentAccountRefusal doesn't recognise the oauth_org_not_allowed type/code or the "Your organization has disabled Claude subscription access" message this PR handles. Without a mapping (e.g. to oauth_organization_disabled), an account refused this way gets #7175's 10-minute cooldown but never auto-pauses. Whichever lands second should add that case.

@vadymhimself

Copy link
Copy Markdown
Contributor Author

Thanks — agreed. If #7153 lands first I'll rebase this PR and map oauth_org_not_allowed (type/code and the whole-message form) to oauth_organization_disabled in classifyPermanentAccountRefusal, with a test that a repeated org-disabled refusal auto-pauses. If this one lands first, happy for #7153 to carry that mapping — or I can push it to #7153's branch, whichever you prefer.

@vadymhimself

Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor
✅ Action performed

Full review finished.

@github-actions github-actions Bot added the intake: hygiene-blocked Deterministic PR hygiene checks failed label Oct 15, 2026
@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Deterministic hygiene checks failed.

  • unsponsored_surface — This changes an authentication, workflow, release-automation, or dependency surface. MAINTAINERS.md requires security review for these; ask a maintainer to apply maintainer-sponsored once they have reviewed it. Paths: src/oauth/anthropic-account-refusal.ts.

@vadymhimself

Copy link
Copy Markdown
Contributor Author

For triage: this is one of a set of small, single-concern fixes from running dev on a busy Claude Code + Codex gateway (one Mac mini, 6-account Anthropic pool, combo/waterfall). All are drafts with the readiness checklist ticked; the fork-PR workflows (hygiene/label/resolve-pr) are waiting for approval to run. Each was proven red on dev and green on its branch; dev + all of them has been serving that gateway since 2026-10-15 21:30 KST+1 with zero doomed sends.

Quota / cache: #7176 (caller 1h TTL on the translated lane — live: post-idle cache write 89,511 → 7,255), #7193 (pool rotation re-binds the reasoning-replay owner), #7177 (declared client refusal → its 4xx, not a retryable 502), #7178 (Claude ingress keeps max_tokens on non-canonical Responses gateways), #7175 (org-disabled 403 rotates), #7191 (generic-OAuth pool 429 cools the account, not the combo target).
Accounting / visibility: #7179 + #7199 (sidecar-loop iterations and errored iterations reach the ledger), #7189 (no confident price on partly measured usage), #7190 (retried non-combo rows total all attempts), #7185 + #7197 (403 / Codex account-move recovery kinds), #7187 (local admission refusal says so), #7198 (routing health: aborted streams fail, no-send rows aren't evidence).
Other: #7194 (typed web_search/web_fetch on the translated lane to api.anthropic.com), #7200 (launchd repair waits for the booted-out job — it stranded a serving service in production).

Happy to rebase, split or merge any of these into a carry, whatever is easiest.

@github-actions github-actions Bot added the bug Something isn't working label Oct 15, 2026
@github-actions

github-actions Bot commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

⏳ DRAFT

  • hygiene: unsponsored_surface.

What to do

  • Fix unsponsored_surface — This changes an authentication, workflow, release-automation, or dependency surface. MAINTAINERS.md requires security review for these; ask a maintainer to apply maintainer-sponsored once they have reviewed it. Paths: src/oauth/anthropic-account-refusal.ts.
  • Tick all four boxes in the PR description once you're done (currently 0/4).

Review readiness checklist

  • ⬜ 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.

0/4 boxes ticked.

This pull request was already a draft. Its draft status will be preserved after every issue above is resolved.
@vadymhimself Tick the boxes once required local validation has passed with commands, results, and any full-suite exception documented, your branch is on the latest dev commit, and every correct Codex and CodeRabbit finding is resolved.

@vadymhimself
vadymhimself force-pushed the fix/anthropic-org-disabled-rotation branch from 554877a to 3ffd538 Compare October 15, 2026 20:35
…bscription access

Anthropic refuses an OAuth account whose organization disabled Claude
subscription access with HTTP 403 `oauth_org_not_allowed` ("Your organization
has disabled Claude subscription access for Claude Code. ..."). The account
refusal classifier did not recognise it, so the pool kept selecting the
denied account and every turn routed there failed while healthy accounts
were available.

Classify it as an account refusal, by the structured type or code first and
by its whole message under `permission_error`, so the existing 403 path
cools the account for ACCOUNT_REFUSAL_COOLDOWN_MS and rotates. Like the
sibling entitlement refusals it is not marked for reauthentication: the token
is valid and a fresh login cannot lift an organization policy.

Co-Authored-By: Claude Code <noreply@anthropic.com>
@lidge-jun

Copy link
Copy Markdown
Owner

Being carried into #7224 with your authorship preserved; this PR will be closed when the bundle lands.

lidge-jun added a commit that referenced this pull request Oct 16, 2026
…evidence fixes (#7191, #7204, #7187, #7209, #7175, #7185, #7193, #7197, #7198) (#7224)

Carried from #7191, #7204, #7187, #7209, #7175, #7185, #7193, #7197, #7198 (one commit per source PR, original author preserved). Security review follow-up: the Anthropic 402 combo cooldown now validates the sending credential's ownership before cooling an account.

Remote validation (hosted GitHub Actions unreliable; local tests not run by maintainer rule): union candidate codex/prr-integ-8 = dev c03eafd + #7224 + #7223 + #7225 + #7217 + #6454 + #6594 plus a Gentle Shell import-cycle fix (integ-9 e1f289c: typecheck + full suite on mini (Windows) and lidge (Linux), compared with the same host's dev runs) and a test-only Windows path-escaping fix for the reasoning-retention routing test (integ-10 0d4cba7, tree a2b309a: that file re-run on both hosts).

Co-authored-by: Vadym O <bolein95@gmail.com>
@lidge-jun

Copy link
Copy Markdown
Owner

Landed on dev in 6ccb47a via #7224 (your change is its own commit in that bundle, and the squash keeps your authorship as Co-authored-by). Thank you!

@lidge-jun lidge-jun closed this Oct 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working intake: hygiene-blocked Deterministic PR hygiene checks failed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants