Skip to content

feat(runtime): retire the Claude subscription OAuth provider - #3183

Merged
likun666661 merged 12 commits into
apache:mainfrom
Joob1n:feat/retire-claude-subscription-oauth
Aug 21, 2026
Merged

feat(runtime): retire the Claude subscription OAuth provider#3183
likun666661 merged 12 commits into
apache:mainfrom
Joob1n:feat/retire-claude-subscription-oauth

Conversation

@Joob1n

@Joob1n Joob1n commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Summary

Maka could sign in with a Claude Pro/Max subscription and send inference through it. Anthropic's Consumer Terms permit programmatic access to the consumer Claude services only through an API key or explicit permission, and this path had neither — it presented itself as Claude Code (borrowing that client_id, its User-Agent, its beta header set, and an x-app: cli marker) to get requests accepted. The account carrying that risk is the user's, not Maka's.

This removes the capability rather than gating it further.

claude-subscription keeps its registry entry, and the entry is marked retired — deliberately distinct from a provider that was never wired, since both end up with no Runtime adapter but only one of them used to work and needs to explain itself.

Keeping it registered is a product choice, not a technical necessity. Unregistering is possible: isRetiredGeminiCliConnection is the precedent, filtering by raw providerType string before canonical decode. It is avoided here because a user who signed in has a credential on this machine, and the connection is what makes that visible and deletable — unregister it and onboarding reports needs_connection, with the credential still on disk and nothing pointing at it. (decodeProviderType does throw on unknown types and connection-catalog-codec.ts would fail the whole document, so unregistering without the filtering precedent would cost the user every other connection — but that is a reason to use the precedent, not a reason the choice was forced.)

Retirement is refused at each authority that could otherwise admit the connection, so no single revert makes it sendable again:

Authority Behavior
deriveProviderAuthContract every action hidden — this is what makes the storage layer refuse a model fetch or a connection test
resolveExecutionConnection returns provider_retired, a first-class result, so a retained credential can no longer read as ready
SessionCatalogCoordinator refuses before a Session is committed — the path Bot, CLI and scheduled runs take, which never reaches the Desktop readiness gate
isConnectionReady provider_retired, before a Desktop send is admitted
buildConnectionModelCatalogEntries every model resolves to provider_removed, so the pickers stop offering them
resolveModelRuntime refuses ahead of the per-model override lookup, so a generated override row cannot hand a retired provider a working adapter
storage interactive-login allow list no longer names it
OAUTH_LOGIN_PROVIDERS (Host wire) no longer names it
getAIModel throws, as the last backstop

Loading the catalog also releases a defaultTarget that points at a retained retired connection, so new Sessions stop defaulting to something that cannot run. Settings no longer shows a 默认 Badge on one either, since retirement is checked before both default states — a Badge with no control to move the default off it would be exactly the read-only label that slot exists to avoid.

Settings explains the state instead of pointing at a sign-in that no longer exists, and stops offering "set as default" and "test connection". Onboarding gets its own all_connections_retired copy — the generic blocked text tells the user to re-check credentials, sign-in status, and network, and for a retired provider all three lead nowhere. Deleting the connection is what clears the credential this machine still holds.

The impersonation code goes with it: the cloaked request builder, the Claude token endpoint and its client identity, the cloaked model-fetch headers, and the subscription usage/quota path that needed that same identity to read.

RUNTIME_HOST_COMPATIBILITY_EPOCH moves 25 → 26 (main reached 25 while this PR was in review, so the rebase re-took the next slot; the description tracks the actual diff). This PR changes the wire contract (removes claude-subscription from OAUTH_LOGIN_PROVIDERS, deletes the oauth.account.usage.fetch operation), so it takes its own epoch rather than riding one raised elsewhere. An earlier revision of this description claimed a 22 → 23 bump that a rebase had turned into a no-op; that was wrong, and @Astro-Han and @hqhq1025 both caught it.

Refs #3143

Verification

Built from clean (npm run clean first) — an incremental build hid a real error during this work, so every gate below was re-run after a full rebuild.

  • npm run build, npm run typecheck — 0 errors
  • lint, format:check, astryx:surface-inventory, check:stale, check:third-party-notices, check:windows-cargo-notices — pass
  • npm test — the failing set is identical to the same suite on a clean origin/main worktree on this machine (environment-related ripgrep / PTY / filesystem-worker cases). Diffed programmatically rather than eyeballed.

Each enforcement point was mutation-checked: reverting it individually turns a test red.

Reverted Result
runtimeAdapter: 'unavailable' red
retired: true red
providerAvailable retirement branch red
auth-contract retirement branch red
resolveExecutionConnection refusal red
Session admission refusal red — 2 cases (default target, explicit target)
defaultTarget release red
OAUTH_LOGIN_PROVIDERS red
isConnectionReady gate red

One correction to an earlier revision of this table: the storage interactive-login allow-list row does not discriminate on its own — reverting only that line leaves the suite green, because the auth-contract gate independently produces the same refusal. That is defense in depth working as intended, but the row overstated its own power and @Astro-Han was right to flag it.

End-to-end, against a real claude-subscription connection already stored on this machine (packaged app, installed, launched) — see the before/after screenshots below.

Not run: Windows and Linux packaging, and the Playwright E2E suite. The new retired UI states have no Storybook stories.

Breaking change

A user signed in through the Claude subscription can no longer send with it. The connection and its stored credential are left in place and readable so the state is explainable; an Anthropic API key connection is the replacement path. Clients built against an earlier epoch are rejected at the handshake by the bump above.

Follow-up

The request_authorization_code / paste-code presentation has no producer left once this lands, and both @M4n5ter and @hqhq1025 asked for its removal. It is the immediate next PR rather than part of this one; the exact file-by-file scope, what stays because the live device flows import it, and why the unused #exchangeCode field is deliberately left in place here are in this comment.

AI use

Select exactly one:

  • No generative tool made a substantive contribution
  • Generative tooling made a substantive contribution

Tool(s) and scope: Claude Code — drafted the removal, the enforcement points and their tests, and ran the verification described above. Reviewed and submitted by the contributor of record. Generated-by: Claude Code is on the commit.

Checklist

  • Tests cover the change and fail without it
  • Lint, format, typecheck and the affected suites pass locally

Does this PR entail a change in behavior?

  • Yes — described under Summary above
  • No
retire-claude-subscription-before-after

@coderabbitai

coderabbitai Bot commented Aug 18, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

What problem this solves

This PR retires the claude-subscription OAuth provider. It blocks authentication, model discovery, inference, testing, and quota operations through Claude Pro/Max subscriptions.

The provider remains registered as retired. Stored connections remain decodable, visible, and deletable. Users must delete retired connections to remove stored credentials. Anthropic API keys remain the supported replacement.

Source of truth

The PR extends the existing provider registry, readiness, authentication-contract, onboarding, storage-policy, runtime, and UI paths. It does not create a parallel retirement path.

The provider registry is the source of truth for retirement. Runtime and UI behavior derive from that state.

Scope and necessity

This is the smallest coherent solution for provider retirement:

  • It retains the registry entry to preserve stored-connection decoding.
  • It blocks all authentication and execution entry points.
  • It removes Claude-specific token, impersonation, model-fetch, cloaking, usage, and quota code.
  • It updates settings, onboarding, error copy, and model status.
  • It adds contract and regression coverage for retained retired connections.

The removed settings component, usage module, bridge methods, protocol operations, provider-specific helpers, fixtures, and stale export cannot be removed further without weakening compatibility or regression coverage.

The request_authorization_code cleanup is intentionally deferred to a follow-up PR. That follow-up will cover its wire types, desktop IPC, coordinator exchange path, runtime helpers, and path-specific tests.

Complexity delta

The PR removes:

  • One supported OAuth provider and its runtime adapter.
  • Claude-specific authorization, token refresh, model-fetch, cloaking, impersonation, and usage authorities.
  • Quota IPC operations and public bridge methods.
  • Claude-specific settings UI, fixtures, exports, and localization content.
  • Related test-maintenance burden.

The PR adds:

  • The retired provider state.
  • The isRetiredProvider registry helper.
  • The provider_retired readiness and error reason.
  • The all_connections_retired onboarding state.
  • Retirement branches in authentication, model catalogs, runtime resolution, connection testing, session admission, and settings.
  • Contract tests for retained-but-unavailable providers.

Total maintenance complexity decreases. The added retirement state is necessary to preserve stored-connection decoding while preventing use.

Validation and risks

Tests cover readiness rejection, provider contracts, onboarding, connection status, OAuth protocol behavior, storage policy, session admission, runtime rejection without network requests, and supported-provider OAuth flows.

The author reports passing build, typecheck, lint, formatting, and other listed checks. Environment-related failures match clean origin/main. Windows/Linux packaging and Playwright E2E tests were not run. Required-check status remains unverified without direct CI evidence.

Concrete risks include:

  • Retired connections now appear unavailable and require replacement.
  • Preload and runtime-host contracts remove Claude-specific methods and quota operations.
  • RUNTIME_HOST_COMPATIBILITY_EPOCH increases to 25.
  • OAuth protocol and provider unions change.
  • Stored-connection decoding depends on retaining the retired registry entry.
  • Authentication behavior changes from Claude subscription access to Anthropic API-key access.

Review-relevant risks

The current diff affects user-visible settings and onboarding, public bridge and protocol contracts, runtime compatibility, authentication behavior, and stored-connection handling. Material changes in these areas require independent human review under repository policy.

The follow-up for request_authorization_code should be tracked or linked. This is not an implementation request for the current PR.

The person performing the merge reviews the final diff. A maintainer makes the final determination.

Walkthrough

Claude subscription OAuth support is retired across core, runtime, runtime-host, storage, and desktop. Existing connections remain decodable but cannot authenticate, execute models, refresh quota, or initiate OAuth login. The UI directs users to replacement connections.

Changes

Claude subscription retirement

Layer / File(s) Summary
Core retirement contracts and readiness
packages/core/src/...
The provider registry marks claude-subscription as retired and unavailable. Readiness, onboarding, authentication, model catalogs, and repair routing report retired-provider states.
Runtime and runtime-host removal
packages/runtime/src/..., packages/runtime-host/src/...
Claude-specific OAuth login, model execution, discovery, credential handling, authorization-code flows, quota operations, and device identifiers are removed. Codex and xAI flows remain supported.
Desktop and storage policy
apps/desktop/src/..., packages/storage/src/...
Retired connections show dedicated status and guidance. Interactive login, execution, model testing, and default targeting reject retired providers.
Validation and fixtures
packages/*/src/__tests__/*, apps/desktop/stories/...
Tests and fixtures now cover retired-provider behavior and use supported Codex or xAI OAuth flows.

Estimated code review effort: 4 (Complex) | ~60 minutes

Merge Risk: 🟡 Moderate · up to 91c50

The change retires Claude subscription connections, but a retained retired connection can still be selected as the default and then cause new sessions to fail instead of running. Merge should wait for default-target validation to reject retired providers and for the corresponding mutation-path test.

Possibly related issues

  • maka-agent/maka-agent#3143 — The changes directly implement retirement of the claude-subscription OAuth path across registry, runtime, storage, desktop, and compatibility layers.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Ai Use Disclosure ✅ Passed The PR selects generative contribution, names Claude Code and its scope, and its sole introduced commit has the matching standalone trailer Generated-by: Claude Code.
Description check ✅ Passed The description follows the template and clearly documents scope, verification, behavior changes, AI use, and incomplete checks.
Linked Issues check ✅ Passed The description references issue #3143 and links the related follow-up discussion.
Out of Scope Changes check ✅ Passed The changes directly support retiring the Claude subscription provider and match the stated objectives.
Title check ✅ Passed The title is concise, specific, and accurately describes the primary change.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Retire Claude subscription OAuth across runtime and desktop

✨ Enhancement 🐞 Bug fix 🧪 Tests 🕐 40+ Minutes

Grey Divider

AI Description

• Retires Claude subscription OAuth while preserving legacy connection decoding and deletion.
• Blocks retired connections across enrollment, discovery, readiness, testing, and runtime
 execution.
• Removes Claude Code impersonation, quota access, and related desktop surfaces.
Diagram

graph TD
  A["Stored Connection"] --> B["Provider Registry"] --> C["Auth Contract"] --> D["Storage Actions"]
  B --> E["Readiness Gate"] --> F["Runtime Backstop"]
  B --> G["Model Catalog"] --> H["Settings UI"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Purge the provider after catalog migration
  • ➕ Removes the retired provider type and compatibility branches completely.
  • ➕ Reduces the long-term registry and runtime state space.
  • ➖ Requires a tolerant catalog decoder or explicit migration before current clients can load data.
  • ➖ Could discard every connection when an older catalog contains the unknown provider type.
  • ➖ Makes stored credentials and legacy connections harder to expose for deliberate deletion.

Recommendation: Keep the PR’s registered-but-retired approach for immediate safety. It preserves catalog compatibility and credential cleanup while independently blocking enrollment, storage actions, model selection, task admission, and runtime execution. A later schema migration could purge the provider only after tolerant decoding and credential cleanup are guaranteed.

Files changed (65) +635 / -1567

Enhancement (28) +129 / -642
oauth-connection-identities.tsRemove Claude from interactive OAuth identities +0/-1

Remove Claude from interactive OAuth identities

• Drops the Claude subscription slug from the Desktop interactive OAuth provider mapping.

apps/desktop/src/main/oauth-connection-identities.ts

runtime-host-client.tsRemove OAuth account usage client operation +0/-6

Remove OAuth account usage client operation

• Deletes the Runtime Host client method for fetching subscription account usage.

apps/desktop/src/main/runtime-host-client.ts

runtime-host-oauth-ipc-main.tsRemove Claude quota IPC handling +2/-23

Remove Claude quota IPC handling

• Eliminates quota channels, account-usage fetching, quota caching, and quota-bearing account-state projections from Desktop OAuth IPC.

apps/desktop/src/main/runtime-host-oauth-ipc-main.ts

bridge-contract.d.tsRemove Claude subscription preload contract +0/-16

Remove Claude subscription preload contract

• Removes the renderer-facing Claude subscription bridge and its account-state type dependency.

apps/desktop/src/preload/bridge-contract.d.ts

preload.tsRemove Claude OAuth preload bridge +5/-47

Remove Claude OAuth preload bridge

• Deletes all Claude subscription authorization, account-state, quota, refresh, and logout bridge methods while retaining the Codex bridge.

apps/desktop/src/preload/preload.ts

conversation-copy.tsLocalize retired-provider conversation errors +4/-0

Localize retired-provider conversation errors

• Adds Chinese and English blocked-send and task-health copy for connections whose sign-in provider was retired.

apps/desktop/src/renderer/locales/conversation-copy.ts

onboarding-copy.tsAdd dedicated retired-connections onboarding copy +21/-3

Add dedicated retired-connections onboarding copy

• Keys blocked onboarding text by reason and adds Chinese and English guidance for workspaces containing only retired connections.

apps/desktop/src/renderer/locales/onboarding-copy.ts

settings-provider-copy.tsExplain Claude sign-in retirement in settings +6/-0

Explain Claude sign-in retirement in settings

• Adds bilingual retirement messaging recommending Anthropic API keys and explaining that deletion clears the stored credential.

apps/desktop/src/renderer/locales/settings-provider-copy.ts

provider-connection-detail.tsxRender retired connection state without actions +15/-7

Render retired connection state without actions

• Displays an informational retirement banner and suppresses connection testing and model refresh controls for retired providers.

apps/desktop/src/renderer/settings/provider-connection-detail.tsx

provider-oauth-section.tsxRemove Claude from OAuth account settings +4/-38

Remove Claude from OAuth account settings

• Removes the Claude OAuth card, experimental gate, state refresh, login panel, and subscription snapshot integration.

apps/desktop/src/renderer/settings/provider-oauth-section.tsx

use-connection-detail.tsExpose retirement state to connection details +8/-1

Expose retirement state to connection details

• Detects retired providers, disables their OAuth relogin service, and supplies retirement state to the detail view.

apps/desktop/src/renderer/settings/use-connection-detail.ts

connection-error-copy.tsAdd retired-provider repair guidance +2/-0

Add retired-provider repair guidance

• Maps provider_retired to guidance for selecting another model connection.

packages/core/src/connection-error-copy.ts

model-web-search.tsRemove Claude subscription web-search mapping +0/-2

Remove Claude subscription web-search mapping

• Stops assigning Anthropic hosted web-search adapters and defaults to the retired provider.

packages/core/src/model-web-search.ts

onboarding.tsDistinguish retired onboarding blockage +11/-3

Distinguish retired onboarding blockage

• Adds all_connections_retired and derives it when every candidate connection uses a retired provider.

packages/core/src/onboarding.ts

provider-registry.tsMark Claude subscription registered but retired +24/-4

Mark Claude subscription registered but retired

• Replaces its runtime adapter with unavailable, adds explicit retirement metadata and detection, and removes Claude-specific discovery authentication.

packages/core/src/provider-registry.ts

oauth.tsRemove Claude and account usage from OAuth protocol +1/-100

Remove Claude and account usage from OAuth protocol

• Restricts interactive OAuth providers to Codex and xAI and deletes account-usage operation types, codecs, and validation.

packages/runtime-host/src/protocol/oauth.ts

operations.tsRevoke remote OAuth usage operation grant +0/-1

Revoke remote OAuth usage operation grant

• Removes the deleted OAuth account-usage operation from remote owner grants.

packages/runtime-host/src/protocol/operations.ts

execution-composition.tsRemove Claude device identity wiring +0/-5

Remove Claude device identity wiring

• Stops passing the storage-root-derived Claude device identifier into interactive and auxiliary model compositions.

packages/runtime-host/src/server/execution-composition.ts

execution-model-authority.tsRemove Claude identity from auxiliary model authority +0/-5

Remove Claude identity from auxiliary model authority

• Eliminates Claude device identifiers from auxiliary model authority contracts and OAuth model-fetch construction.

packages/runtime-host/src/server/execution-model-authority.ts

execution-model-composition.tsRemove Claude identity from backend composition +0/-2

Remove Claude identity from backend composition

• Drops Claude device identity from Host AI SDK backend inputs and subscription model fetch construction.

packages/runtime-host/src/server/execution-model-composition.ts

oauth-coordinator.tsRemove Claude login and usage coordination +3/-134

Remove Claude login and usage coordination

• Deletes Claude paste-code authorization, token exchange, quota fetching, and associated dependencies, leaving Codex and xAI enrollment flows.

packages/runtime-host/src/server/oauth-coordinator.ts

oauth-execution-authority.tsRemove Claude request identity injection +0/-18

Remove Claude request identity injection

• Eliminates Claude account validation and cloaking metadata from Host OAuth model fetches.

packages/runtime-host/src/server/oauth-execution-authority.ts

model-fetcher.tsRemove cloaked Anthropic model discovery +3/-16

Remove cloaked Anthropic model discovery

• Deletes Claude bearer and impersonation headers so Anthropic model discovery only uses API-key authentication.

packages/runtime/src/model-fetcher.ts

oauth-login.tsRemove Claude authorization and token exchange support +17/-78

Remove Claude authorization and token exchange support

• Restricts authorization-code login to xAI and deletes Claude endpoints, request encoding, token decoding, and fixed redirect handling.

packages/runtime/src/oauth-login.ts

oauth-provider-contracts.tsRemove Claude OAuth client identity +1/-16

Remove Claude OAuth client identity

• Deletes Claude from enrollment providers and removes the borrowed client ID, endpoints, scopes, User-Agent, and experimental flag.

packages/runtime/src/oauth-provider-contracts.ts

subscription-credentials.tsRemove Claude credential refresh support +1/-44

Remove Claude credential refresh support

• Excludes Claude from OAuth subscription credential providers and deletes its token refresh implementation and client contract.

packages/runtime/src/subscription-credentials.ts

subscription-model-fetch.tsRemove Claude cloaked model transport +0/-71

Remove Claude cloaked model transport

• Deletes Claude cloak configuration, metadata validation, and request-rewriting transport while retaining supported subscription providers.

packages/runtime/src/subscription-model-fetch.ts

operations.tsNarrow interactive OAuth ticket providers +1/-1

Narrow interactive OAuth ticket providers

• Removes Claude subscription from the storage operation type for interactive OAuth login tickets.

packages/storage/src/runtime-policy/operations.ts

Bug fix (12) +99 / -71
chat-readiness.tsExplain retired providers at send admission +2/-0

Explain retired providers at send admission

• Adds a localized provider_retired message directing users to select another model connection.

apps/desktop/src/main/chat-readiness.ts

onboarding-hero-copy.tsSelect onboarding copy by blocked reason +9/-7

Select onboarding copy by blocked reason

• Routes blocked onboarding states to reason-specific copy so retired connections do not receive credential-repair guidance.

apps/desktop/src/renderer/onboarding-hero-copy.ts

providers-panel.tsxPrevent retired connections becoming defaults +5/-0

Prevent retired connections becoming defaults

• Suppresses the set-as-default action for retired provider connections.

apps/desktop/src/renderer/settings/providers-panel.tsx

connection-readiness.tsReject retired providers before credential checks +18/-9

Reject retired providers before credential checks

• Adds provider_retired to canonical readiness reasons and prioritizes it before disabled, credential, and model validation.

packages/core/src/connection-readiness.ts

model-catalog.tsMark retired-provider models unavailable +5/-1

Mark retired-provider models unavailable

• Forces models belonging to retired providers to resolve as provider_removed even when callers report the provider available.

packages/core/src/model-catalog.ts

provider-auth.tsHide every action for retired providers +24/-0

Hide every action for retired providers

• Introduces a retired-provider auth contract with no setup mode or available actions while preserving deletion-based credential cleanup.

packages/core/src/provider-auth.ts

task-submission-readiness.tsRoute retired tasks to provider catalog +3/-0

Route retired tasks to provider catalog

• Directs provider_retired repair actions to the provider catalog rather than an irreparable connection detail page.

packages/core/src/task-submission-readiness.ts

model-factory.tsBackstop retired providers during model construction +7/-10

Backstop retired providers during model construction

• Removes Claude subscription model creation and headers, adds an unavailable-adapter failure, and removes retired-provider thinking options.

packages/runtime/src/model-factory.ts

model-runtime.tsReject unavailable runtime adapters +5/-1

Reject unavailable runtime adapters

• Throws when runtime wire resolution reaches a retired provider’s unavailable adapter.

packages/runtime/src/model-runtime.ts

subscription-auth.tsRemove Claude Code impersonation headers +0/-13

Remove Claude Code impersonation headers

• Deletes Claude-specific beta headers, CLI User-Agent, direct-browser marker, and x-app identity generation.

packages/runtime/src/subscription-auth.ts

test-connection.tsFail retired connection tests without network access +20/-25

Fail retired connection tests without network access

• Returns an explicit retirement result for unavailable adapters and removes the former token-presence success path and Claude headers.

packages/runtime/src/test-connection.ts

coordinator.tsRemove Claude from interactive login admission +1/-5

Remove Claude from interactive login admission

• Restricts storage-authorized interactive OAuth login providers to Codex and xAI.

packages/storage/src/runtime-policy/coordinator.ts

Refactor (1) +7 / -139
oauth-subscription.tsGeneralize remaining OAuth subscription helpers +7/-139

Generalize remaining OAuth subscription helpers

• Removes Claude account, quota, runtime-state, and authorization configuration contracts while retaining provider-neutral action and PKCE utilities.

packages/core/src/oauth-subscription.ts

Tests (23) +399 / -714
chat-readiness.test.tsCover retired-provider chat readiness +20/-4

Cover retired-provider chat readiness

• Moves the active OAuth credential case to Codex and verifies Claude subscription reports provider_retired even when a credential remains stored.

apps/desktop/src/main/tests/chat-readiness.test.ts

runtime-host-account-connection.test.tsUse Codex for account synchronization tests +11/-11

Use Codex for account synchronization tests

• Replaces Claude subscription fixtures and model expectations with the remaining Codex OAuth provider.

apps/desktop/src/main/tests/runtime-host-account-connection.test.ts

runtime-host-oauth-ipc-main.test.tsRemove Claude OAuth and quota IPC expectations +12/-29

Remove Claude OAuth and quota IPC expectations

• Moves shared OAuth IPC coverage to Codex, removes Claude channels and quota refresh assertions, and verifies account state without quota data.

apps/desktop/src/main/tests/runtime-host-oauth-ipc-main.test.ts

provider-settings.stories.tsxRemove Claude subscription story fixture +0/-13

Remove Claude subscription story fixture

• Deletes the obsolete Claude OAuth bridge fixture from provider settings stories.

apps/desktop/stories/settings/provider-settings.stories.tsx

settings-pages.stories.tsxRemove Claude account settings fixture +0/-7

Remove Claude account settings fixture

• Removes the authenticated Claude subscription fixture from settings-page stories.

apps/desktop/stories/settings/settings-pages.stories.tsx

connection-readiness.test.tsTest canonical retired-provider readiness +31/-0

Test canonical retired-provider readiness

• Verifies retirement blocks sending regardless of whether a legacy OAuth credential remains stored.

packages/core/src/tests/connection-readiness.test.ts

oauth-subscription.test.tsRemove Claude authorization URL tests +0/-33

Remove Claude authorization URL tests

• Drops tests for the removed Claude-specific OAuth authorization URL builder while retaining provider-neutral PKCE helpers.

packages/core/src/tests/oauth-subscription.test.ts

onboarding.test.tsTest all-connections-retired onboarding +22/-2

Test all-connections-retired onboarding

• Moves healthy OAuth coverage to xAI and verifies a Claude-only workspace receives the dedicated retirement reason.

packages/core/src/tests/onboarding.test.ts

provider-auth.test.tsMove active OAuth auth tests off Claude +2/-3

Move active OAuth auth tests off Claude

• Uses xAI and Codex for active OAuth contract scenarios now that Claude subscription is retired.

packages/core/src/tests/provider-auth.test.ts

provider-catalog-contract.test.tsPin retired-provider safety invariants +91/-1

Pin retired-provider safety invariants

• Verifies retired providers remain registered but unwired, stay out of creation catalogs, expose no actions, and yield unselectable removed models.

packages/core/src/tests/provider-catalog-contract.test.ts

execution-model-composition.test.tsMove OAuth execution composition coverage to Codex +36/-49

Move OAuth execution composition coverage to Codex

• Reworks OAuth refresh and backend composition fixtures around Codex, including credential login, model-fetch seeding, and account identity preservation.

packages/runtime-host/src/tests/execution-model-composition.test.ts

oauth-coordinator.test.tsRemove Claude enrollment coordinator coverage +16/-171

Remove Claude enrollment coordinator coverage

• Deletes Claude paste-code and account-usage scenarios and moves shared coordinator concurrency and admission tests to Codex or xAI.

packages/runtime-host/src/tests/oauth-coordinator.test.ts

oauth-execution-authority.test.tsRemove Claude cloaked execution tests +28/-151

Remove Claude cloaked execution tests

• Deletes Claude refresh and request-identity coverage, moves lease reconciliation to Codex, and removes Claude device metadata from model fetches.

packages/runtime-host/src/tests/oauth-execution-authority.test.ts

oauth-protocol.test.tsVerify retired OAuth wire values are rejected +36/-38

Verify retired OAuth wire values are rejected

• Replaces quota projection validation with operation-removal coverage and verifies login projections reject Claude subscription.

packages/runtime-host/src/tests/oauth-protocol.test.ts

oauth-two-client-uds.test.tsUse Codex for multi-client OAuth transport tests +22/-16

Use Codex for multi-client OAuth transport tests

• Converts real-endpoint enrollment coverage from Claude paste-code login to Codex device authorization and removes Claude opt-out cases.

packages/runtime-host/src/tests/oauth-two-client-uds.test.ts

claude-subscription-runtime.test.tsVerify runtime refuses retired Claude connections +13/-9

Verify runtime refuses retired Claude connections

• Replaces successful Claude connection-test expectations with explicit retirement failures and confirms no network probe occurs.

packages/runtime/src/tests/claude-subscription-runtime.test.ts

computer-use-model-loop.test.tsRemove Claude subscription from computer-use matrix +0/-1

Remove Claude subscription from computer-use matrix

• Excludes the retired provider from Anthropic-compatible computer-use loop coverage.

packages/runtime/src/tests/computer-use-model-loop.test.ts

model-factory-thinking.test.tsRemove Claude from thinking-option matrix +1/-6

Remove Claude from thinking-option matrix

• Stops treating the retired provider as an active Anthropic-compatible model-factory target.

packages/runtime/src/tests/model-factory-thinking.test.ts

oauth-login.test.tsRemove Claude OAuth login coverage +19/-55

Remove Claude OAuth login coverage

• Deletes Claude authorization and token-decoding tests and moves generic exchange validation to the remaining xAI loopback flow.

packages/runtime/src/tests/oauth-login.test.ts

provider-contract-matrix.tsRemove Claude runtime wire adapter classification +1/-1

Remove Claude runtime wire adapter classification

• Drops claude-subscription from provider-specific subscription wire adapter kinds.

packages/runtime/src/tests/provider-contract-matrix.ts

subscription-credentials.test.tsMove OAuth refresh transaction tests to Codex +25/-28

Move OAuth refresh transaction tests to Codex

• Replaces Claude provider and credential slugs with Codex throughout refresh validation, CAS, concurrency, and logout tests.

packages/runtime/src/tests/subscription-credentials.test.ts

subscription-model-fetch.test.tsRemove Claude cloaked request tests +0/-85

Remove Claude cloaked request tests

• Deletes Claude Code request impersonation and metadata validation scenarios while retaining Codex subscription transport coverage.

packages/runtime/src/tests/subscription-model-fetch.test.ts

runtime-policy-stores.test.tsVerify storage refuses retired OAuth login +13/-1

Verify storage refuses retired OAuth login

• Moves active login-ticket coverage to Codex and asserts a stored Claude connection receives hidden-action refusal.

packages/storage/src/tests/runtime-policy-stores.test.ts

Other (1) +1 / -1
index.tsAdvance Runtime Host compatibility epoch +1/-1

Advance Runtime Host compatibility epoch

• Bumps the compatibility epoch from 22 to 23 for the OAuth provider enum and operation-set changes.

packages/runtime-host/src/protocol/index.ts

@Joob1n
Joob1n force-pushed the feat/retire-claude-subscription-oauth branch from 16f779f to 16c9576 Compare August 18, 2026 03:03
@qodo-code-review

qodo-code-review Bot commented Aug 18, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. Stale package.json export for deleted module ✓ Resolved 🐞 Bug ⚙ Maintainability
Description
The PR deletes packages/runtime/src/claude-subscription-usage.ts, but
packages/runtime/package.json still declares the subpath export `"./claude-subscription-usage":
"./dist/claude-subscription-usage.js"`. Any remaining import of
@maka/runtime/claude-subscription-usage will now resolve to a build artifact that will never be
produced again, failing at module resolution/runtime instead of being caught at compile time.
Code

packages/runtime/src/claude-subscription-usage.ts[L1-5]

-import type { QuotaSnapshot, QuotaWindow } from '@maka/core/oauth-subscription';
-import {
-  OAuthTokenEndpointError,
-  OAUTH_PROVIDER_CONTRACTS,
-  requireOAuthDataRecord,
Relevance

●●● Strong

A deleted module with a dangling package export is a concrete resolution bug; repository reviews
accept similarly defensive boundary fixes.

PR-#3169

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The file was fully deleted by this PR (packages_runtime_src_claude-subscription-usage_ts.patch shows
-64/+0), and the only importer, packages/runtime-host/src/server/oauth-coordinator.ts, dropped its
import { fetchClaudeSubscriptionUsage } from '@maka/runtime/claude-subscription-usage' line. Yet
packages/runtime/package.json line 44 still maps that subpath to
./dist/claude-subscription-usage.js, which will no longer exist after the build removes the
deleted source file, so the entry is dangling.

packages/runtime/package.json[44-44]
packages/runtime/src/claude-subscription-usage.ts: TBD

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The PR deletes `packages/runtime/src/claude-subscription-usage.ts` but leaves the corresponding subpath export in `packages/runtime/package.json` pointing at a build artifact (`./dist/claude-subscription-usage.js`) that will no longer be produced.

## Issue Context
This is part of a larger PR retiring the `claude-subscription` OAuth provider. All source-level imports of this module were removed, but the package.json `exports` map was not updated to match, so consumers relying on this subpath (in-repo or external) would fail to resolve it after a clean build.

## Fix Focus Areas
- packages/runtime/package.json[44-44]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

2. Orphaned Claude settings copy left behind ✓ Resolved 🐞 Bug ⚙ Maintainability
Description
claude-subscription-card.tsx was deleted entirely, but its dedicated claude copy object
(login/logout/quota/status strings) remains in both zhCopy and enCopy in
settings-provider-copy.ts, so ProviderSettingsCopy still carries a field nothing reads anymore.
This is dead data that will mislead future maintainers into thinking the Claude card copy is still
live.
Code

apps/desktop/src/renderer/locales/settings-provider-copy.ts[R57-59]

+    oauthRetired: '此登录方式已停用',
+    oauthRetiredDetail:
+      '这条连接使用的登录方式已从 Maka 移除,无法再登录,也无法用于对话。改用 Anthropic API Key 连接即可继续使用 Claude 模型;删除这条连接会一并清除本机保存的登录凭据。',
Relevance

●●● Strong

Dead localized copy after deleting its sole consumer is a straightforward maintainability cleanup
matching accepted cleanup-oriented review behavior.

PR-#3147

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Repository search confirms the claude object (distinct from the
oauthSection.claudeDescription/claudeSubtitle fields this PR did remove) still exists at
settings-provider-copy.ts:175-191 (zh) and :326-341 (en), fully intact with zero remaining
references (.claude. / copy.claude / getProviderSettingsCopy(locale).claude all return no
matches) after claude-subscription-card.tsx, its only consumer, was deleted.

apps/desktop/src/renderer/locales/settings-provider-copy.ts[175-191]
apps/desktop/src/renderer/locales/settings-provider-copy.ts[326-341]
apps/desktop/src/renderer/settings/claude-subscription-card.tsx: TBD

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The `claude` copy sub-object in `ProviderSettingsCopy` (used only by the now-deleted `ClaudeSubscriptionCard` component) is now completely unreferenced dead code.

## Issue Context
The PR deletes `claude-subscription-card.tsx` and the `oauthSection.claudeDescription`/`claudeSubtitle` fields it used, but missed removing the separate top-level `claude` object that held the card's detailed login/quota copy strings.

## Fix Focus Areas
- apps/desktop/src/renderer/locales/settings-provider-copy.ts[175-191]
- apps/desktop/src/renderer/locales/settings-provider-copy.ts[326-341]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Login provider branch not future-exhaustive ✓ Resolved 🐞 Bug ⚙ Maintainability
Description
#runLogin in oauth-coordinator.ts now branches with `attempt.provider === 'xai-oauth' ?
runXaiLogin : runCodexDeviceLogin`, defaulting every non-xai value to the Codex device flow instead
of using a case-per-value check. This works today because OAuthLoginProvider has only two members,
but a future addition to that union would silently be routed to Codex login with no compiler error —
the opposite of the fail-closed, exhaustively-checked pattern this same PR establishes for adapter
kinds (deriveProviderAuthContract, resolveModelRuntimeWire, getAIModel).
Code

packages/runtime-host/src/server/oauth-coordinator.ts[R330-333]

      const tokens =
        attempt.provider === 'xai-oauth'
          ? await this.#runXaiLogin(attempt, transport.fetch)
-          : attempt.provider === 'openai-codex'
-            ? await this.#runCodexDeviceLogin(attempt, transport.fetch)
-            : await this.#runAuthorizationCodeLogin(attempt, transport.fetch);
+          : await this.#runCodexDeviceLogin(attempt, transport.fetch);
Relevance

●● Moderate

Exhaustiveness concern is technically plausible, but history lacks a close precedent for rejecting
or requiring this specific branch style.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
packages/runtime-host/src/protocol/oauth.ts now declares `OAUTH_LOGIN_PROVIDERS = ['openai-codex',
'xai-oauth']. The coordinator's simplified ternary (attempt.provider === 'xai-oauth' ? ... :
this.#runCodexDeviceLogin(...)`) replaces what used to be a three-way branch (xai / codex / claude
authorization-code) with an implicit-default ternary not tied to the type system.

packages/runtime-host/src/server/oauth-coordinator.ts[330-333]
packages/runtime-host/src/protocol/oauth.ts[16-16]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The two-way ternary that picks the login flow by `attempt.provider` silently routes any provider other than `xai-oauth` to the Codex device flow, with no compiler-enforced exhaustiveness check.

## Issue Context
This PR removed the third (`claude-subscription`) branch of what used to be a three-way conditional, collapsing the remainder into a ternary. Not a bug today given the two-member union, but it drops the fail-closed pattern used elsewhere in the same PR (e.g. adapter-kind switches that explicitly handle `unavailable`).

## Fix Focus Areas
- packages/runtime-host/src/server/oauth-coordinator.ts[330-333]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources
Review mode: 🧠 Deep: This retires a security-sensitive OAuth capability across many independent authorities and runtime, storage, IPC, UI, and compatibility paths, creating a dense set of easy-to-miss regressions.

Grey Divider

Tip of the day
💡 Did you know, you can show, collapse, or hide each part of a finding: code, evidence, and all

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

@coderabbitai coderabbitai 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.

Actionable comments posted: 3

🧹 Nitpick comments (3)
packages/runtime-host/src/server/oauth-coordinator.ts (1)

2-2: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Delete the unused authorization-code exchange dependency.

Disposition: optional.

After Line 333, #runLogin calls only #runXaiLogin or #runCodexDeviceLogin. Neither method calls #exchangeCode. Delete exchangeOAuthAuthorizationCode, HostOAuthCoordinatorInput.exchangeCode, and the private #exchangeCode field.

As per path instructions, “Flag concrete cases where code can be deleted or simplified.”

Also applies to: 333-333

Source: Path instructions

packages/runtime-host/src/__tests__/oauth-execution-authority.test.ts (1)

439-439: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove the duplicated union member.

'openai-codex' | 'openai-codex' is equivalent to one literal type. Declare providerType as 'openai-codex'.

Disposition: optional.

Proposed simplification
-  providerType: 'openai-codex' | 'openai-codex',
+  providerType: 'openai-codex',

As per path instructions, “Flag concrete cases where code can be deleted or simplified.”

Source: Path instructions

packages/runtime/src/__tests__/oauth-login.test.ts (1)

104-112: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Delete the unused Claude environment entry.

MAKA_CLAUDE_SUBSCRIPTION_EXPERIMENTAL affects neither assertion. The test only evaluates openai-codex and xai-oauth, and xAI ignores environment flags. Remove this entry to keep the test limited to active enrollment behavior. Disposition: optional.

As per path instructions, delete an unnecessary path before adding behavior.

Source: Path instructions


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 000f121d-fee4-4262-991f-407da136b587

📥 Commits

Reviewing files that changed from the base of the PR and between e685853 and 16c9576.

📒 Files selected for processing (71)
  • apps/desktop/src/main/__tests__/chat-readiness.test.ts
  • apps/desktop/src/main/__tests__/runtime-host-account-connection.test.ts
  • apps/desktop/src/main/__tests__/runtime-host-oauth-ipc-main.test.ts
  • apps/desktop/src/main/chat-readiness.ts
  • apps/desktop/src/main/oauth-connection-identities.ts
  • apps/desktop/src/main/runtime-host-client.ts
  • apps/desktop/src/main/runtime-host-oauth-ipc-main.ts
  • apps/desktop/src/preload/bridge-contract.d.ts
  • apps/desktop/src/preload/preload.ts
  • apps/desktop/src/renderer/locales/conversation-copy.ts
  • apps/desktop/src/renderer/locales/onboarding-copy.ts
  • apps/desktop/src/renderer/locales/settings-provider-copy.ts
  • apps/desktop/src/renderer/onboarding-hero-copy.ts
  • apps/desktop/src/renderer/settings/claude-subscription-card.tsx
  • apps/desktop/src/renderer/settings/provider-connection-detail.tsx
  • apps/desktop/src/renderer/settings/provider-oauth-section.tsx
  • apps/desktop/src/renderer/settings/providers-panel.tsx
  • apps/desktop/src/renderer/settings/use-connection-detail.ts
  • apps/desktop/stories/settings/provider-settings.stories.tsx
  • apps/desktop/stories/settings/settings-pages.stories.tsx
  • docs/astryx-surface-file-inventory.md
  • docs/astryx-surface-file-inventory.paths
  • packages/core/src/__tests__/connection-readiness.test.ts
  • packages/core/src/__tests__/oauth-subscription.test.ts
  • packages/core/src/__tests__/onboarding.test.ts
  • packages/core/src/__tests__/provider-auth.test.ts
  • packages/core/src/__tests__/provider-catalog-contract.test.ts
  • packages/core/src/connection-error-copy.ts
  • packages/core/src/connection-readiness.ts
  • packages/core/src/model-catalog.ts
  • packages/core/src/model-web-search.ts
  • packages/core/src/oauth-subscription.ts
  • packages/core/src/onboarding.ts
  • packages/core/src/provider-auth.ts
  • packages/core/src/provider-registry.ts
  • packages/core/src/task-submission-readiness.ts
  • packages/runtime-host/src/__tests__/execution-model-composition.test.ts
  • packages/runtime-host/src/__tests__/oauth-coordinator.test.ts
  • packages/runtime-host/src/__tests__/oauth-execution-authority.test.ts
  • packages/runtime-host/src/__tests__/oauth-protocol.test.ts
  • packages/runtime-host/src/__tests__/oauth-two-client-uds.test.ts
  • packages/runtime-host/src/protocol/index.ts
  • packages/runtime-host/src/protocol/oauth.ts
  • packages/runtime-host/src/protocol/operations.ts
  • packages/runtime-host/src/server/execution-composition.ts
  • packages/runtime-host/src/server/execution-model-authority.ts
  • packages/runtime-host/src/server/execution-model-composition.ts
  • packages/runtime-host/src/server/oauth-coordinator.ts
  • packages/runtime-host/src/server/oauth-execution-authority.ts
  • packages/runtime/src/__tests__/claude-subscription-runtime.test.ts
  • packages/runtime/src/__tests__/claude-subscription-usage.test.ts
  • packages/runtime/src/__tests__/computer-use-model-loop.test.ts
  • packages/runtime/src/__tests__/model-factory-thinking.test.ts
  • packages/runtime/src/__tests__/oauth-login.test.ts
  • packages/runtime/src/__tests__/provider-contract-matrix.ts
  • packages/runtime/src/__tests__/subscription-credentials.test.ts
  • packages/runtime/src/__tests__/subscription-model-fetch.test.ts
  • packages/runtime/src/claude-subscription-usage.ts
  • packages/runtime/src/model-factory.ts
  • packages/runtime/src/model-fetcher.ts
  • packages/runtime/src/model-runtime.ts
  • packages/runtime/src/oauth-login.ts
  • packages/runtime/src/oauth-provider-contracts.ts
  • packages/runtime/src/subscription-auth.ts
  • packages/runtime/src/subscription-cloaked-request.ts
  • packages/runtime/src/subscription-credentials.ts
  • packages/runtime/src/subscription-model-fetch.ts
  • packages/runtime/src/test-connection.ts
  • packages/storage/src/__tests__/runtime-policy-stores.test.ts
  • packages/storage/src/runtime-policy/coordinator.ts
  • packages/storage/src/runtime-policy/operations.ts
💤 Files with no reviewable changes (21)
  • docs/astryx-surface-file-inventory.paths
  • apps/desktop/src/main/oauth-connection-identities.ts
  • apps/desktop/stories/settings/provider-settings.stories.tsx
  • apps/desktop/stories/settings/settings-pages.stories.tsx
  • apps/desktop/src/renderer/settings/claude-subscription-card.tsx
  • packages/runtime/src/claude-subscription-usage.ts
  • packages/runtime/src/tests/computer-use-model-loop.test.ts
  • packages/runtime-host/src/protocol/operations.ts
  • packages/runtime/src/tests/subscription-model-fetch.test.ts
  • packages/runtime-host/src/server/execution-model-composition.ts
  • apps/desktop/src/preload/bridge-contract.d.ts
  • packages/core/src/model-web-search.ts
  • packages/runtime/src/subscription-cloaked-request.ts
  • packages/core/src/tests/oauth-subscription.test.ts
  • packages/runtime-host/src/server/execution-composition.ts
  • packages/runtime-host/src/server/execution-model-authority.ts
  • packages/runtime/src/tests/claude-subscription-usage.test.ts
  • apps/desktop/src/main/runtime-host-client.ts
  • packages/runtime-host/src/server/oauth-execution-authority.ts
  • packages/runtime/src/subscription-auth.ts
  • packages/runtime/src/subscription-model-fetch.ts

Included review availability: Your plan includes up to 3 reviews per rolling hour; 1 remains after this review.

Comment thread apps/desktop/src/main/__tests__/runtime-host-oauth-ipc-main.test.ts
Comment thread packages/runtime/src/__tests__/provider-contract-matrix.ts
Comment thread packages/runtime/src/model-runtime.ts
@Joob1n
Joob1n force-pushed the feat/retire-claude-subscription-oauth branch from 16c9576 to 2ddd92d Compare August 18, 2026 03:54
@Joob1n

Joob1n commented Aug 18, 2026

Copy link
Copy Markdown
Contributor Author

All three are real. Fixed in the amended commit.

1. Stale package.json export. Correct, and the one I am most glad you caught — neither tsc nor Biome sees a subpath export pointing at a source file that no longer exists. Removed. I then scanned every workspace's exports map against the files this PR deletes; that was the only dangling entry.

2. Orphaned Claude settings copy. Removed the whole claude block from both zhCopy and enCopy, plus claudeDescription and claudeSubtitle, which lost their last reader when the OAuth card went.

3. Login branch not future-exhaustive. Agreed — the ternary was the one place in this PR that fails open. Replaced with a switch in #runProviderLogin. Verified it now fails closed: adding a third member to OAUTH_LOGIN_PROVIDERS makes the compiler reject the function (TS2366) instead of silently routing it to the Codex device flow.

On the alternative approach: agreed, and the reason is stronger than compatibility. connection-catalog-document.ts decodes provider types with a plain .map(), so an unregistered type fails the whole document — a user would lose every other connection along with this one. That is why the entry stays registered and marked retired.

@Joob1n
Joob1n force-pushed the feat/retire-claude-subscription-oauth branch 2 times, most recently from fd810dd to 1d978df Compare August 18, 2026 05:53
@Astro-Han

Copy link
Copy Markdown
Contributor

Thanks for the removal — the impersonation is unambiguous and the decision is right: the deleted subscription-cloaked-request.ts fabricated the Claude Code identity end to end (UA claude-cli/2.1.153 (external, cli), x-app: cli, X-Claude-Code-Session-Id, a spoofed system prompt "You are Claude Code…", billing markers, Stainless client headers, and the borrowed client_id), and the risk lands on the user's Anthropic account under Consumer Terms — removing beats gating because gating only hides the violation behind a flag. The 7 enforcement points are real and mutually independent (registry truth → storage admission → send readiness → picker → OAuth login → wire enum → runtime backstop); I verified each one exists at its claimed location and that mutating it individually turns a test red. The delete is clean (4 files removed wholesale, shared infra correctly trimmed, no dangling imports; the stale package.json export is fixed). The retained-but-retired registry entry is justified — decodeProviderType throws on unknown types and the whole catalog document fails (connection-catalog-codec.ts:568), so unregistering would cost users every other connection — though note the repo does have a full-unregistration precedent (isRetiredGeminiCliConnection) that filters by raw providerType string before canonical decode, so "impossible" is slightly overstated; the real reason (keep the connection visible and deletable so the user can clear the credential, and onboarding returns needs_connection otherwise) is a fine product choice that should be stated honestly in the PR. The breaking-change note is honest apart from the epoch paragraph below. CI is green.

Conclusion: PASS with one P2 (epoch) — and the UI/UX screenshot gate must be satisfied before merge.

P2 — the epoch bump the PR claims ("RUNTIME_HOST_COMPATIBILITY_EPOCH moves to 23") is not in the diff. This PR changes wire protocol (removes claude-subscription from OAUTH_LOGIN_PROVIDERS and deletes the oauth.account.usage.fetch operation), but protocol/index.ts is untouched: the base already had 23 (raised by #3145, merged the same day), and current main sits at 24 (raised by #3165). So the "old clients rejected by the epoch gate" protection is coincidence, not this PR — a client built between #3145 and this PR (epoch 23, old protocol) would pass the handshake (exact-equality check at connection.ts:1290) and then fail mid-flight on oauth.account.usage.fetch/login with an unknown-operation error instead of a clean handshake rejection. Actual impact is low today (no released epoch-23 client), but the repo's convention is every breaking PR bumps its own epoch, and the description misleads maintainers into thinking the gate covers this. Please bump to 25 in this PR (coordinating with #3103, which also aims at 24→25) and correct the description, or explicitly state you're riding #3145's bump release-atomically and fix the wording.

P3-1 (merge gate): no before/after screenshots for the UI/UX changes. This PR visibly changes Settings (retirement banner, removed test/set-default buttons, list-row retired state), onboarding (all_connections_retired card), and deletes the 494-line subscription card — but the PR has no screenshot/recording. Per the repo review gate, UI/UX changes require before/after screenshots before merge. Please add them.

P3 (optional): one mutation-claim is not independently reproducible — the storage allow-list test (runtime-policy-stores.test.ts:2849-2859) would stay green if only the allow-list revert (the auth-contract gate independently produces the same result — defense-in-depth working as intended, but the mutation table overstates that row's discriminating power); dead code remains in oauth-coordinator.ts (exchangeOAuthAuthorizationCode import, #exchangeCode field, HostOAuthCoordinatorInput.exchangeCode — never called after the code-flow removal); oauth-login.test.ts:106 still sets the removed MAKA_CLAUDE_SUBSCRIPTION_EXPERIMENTAL env flag; the new retired UI states have no Storybook stories and the E2E suite wasn't run (per repo convention, UI states want deterministic Storybook/Playwright coverage).


AI-assisted review disclosure: this review was produced with AI assistance (two independent pi review subagents on opencode-go/deepseek-v4-flash). Both verified the enforcement points, the delete thoroughness, and the catalog decode rationale from the PR head; the epoch finding is a fact of the current tree (base already 23, main 24, diff empty for protocol/index.ts), not a prediction. P3-1 is from the PR having no screenshots. Please weigh these findings with your own judgment.

中文摘要(AI 辅助审查)

结论:PASS(1 个 P2 + UI/UX 截图 gate 阻塞)。冒充确凿、移除决定正确(subscription-cloaked-request.ts 全套伪造 Claude Code 身份——UA/x-app:cli/会话头/假系统提示/计费标记/借用的 client_id,风险在用户自己的 Anthropic 账户,gating 只是把违规路径藏起来,移除才消除风险载体)。7 个 enforcement point 全部真实且相互独立(注册表真相源→storage 准入→send 准入→picker→OAuth 登录→wire 枚举→runtime 兜底),逐一核实存在且单独 mutation 会红。删除干净(4 文件整删、共享基建正确裁剪、无悬空 import、stale export 已修)。retained-but-retired 的技术理由基本成立(decodeProviderType 对未注册类型 throw、整个 catalog 文档失败,unregister 会让用户失去全部连接)——但有 gemini-cli 先例(按原始 providerType 字符串先过滤)证明完全 unregister 可行,"不可能"略夸大;真实理由(保留可见可删、让用户清理本机凭据、否则 onboarding 退回 needs_connection)是合理产品选择,建议在 PR 中诚实陈述。breaking-change 叙述除 epoch 段外诚实。P2:PR 声称的 epoch bump(22→23)不在 diff 里——protocol/index.ts 未动,base 已是 23(#3145 升的),main 当前 24(#3165 升的);"旧客户端被 epoch gate 拒绝"是巧合保护——#3145 与本 PR 之间构建的 client(epoch 23、旧协议)会通过精确相等握手(connection.ts:1290)然后中途在 oauth.account.usage.fetch/login 上报 unknown-operation 而非握手期干净拒绝。实际影响低(无已发布 epoch-23 client),但违反仓库"每个 breaking PR 自己 bump"惯例且描述误导。建议本 PR bump 25(与 #3103 协调)并修正描述,或显式声明骑 #3145 的 bump 并 release-atomic。P3-1(合并 gate):大量 UI/UX 变化(Settings 退休横幅/移除 test 与 set-default 按钮/list 行 retired 状态/onboarding all_connections_retired 卡片/删 494 行卡片)但无任何截图或录屏——按仓库规则 UI/UX 变化合并前必须 before/after 截图。P3(可选):storage allow-list 的 mutation 声称不可独立复现(contract 门独立产生同结果,防御纵深生效但表格高估该行判别力);oauth-coordinator.ts 残留死代码(exchangeOAuthAuthorizationCode 导入、#exchangeCode 字段、HostOAuthCoordinatorInput.exchangeCode 永不被调用);oauth-login.test.ts:106 仍设置已删除的 MAKA_CLAUDE_SUBSCRIPTION_EXPERIMENTAL env flag;新增 retired UI 状态无 Storybook stories、E2E 未跑(仓库惯例 UI 状态要确定性 Storybook/Playwright 覆盖)。

@M4n5ter M4n5ter left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

English

Requesting changes.

Correction: the compatibility-epoch issue was already reported in the top-level review comment from Astro-Han, so I removed my duplicate inline comment. That earlier comment also captures the current-main coordination correctly: main already uses epoch 24, so this PR needs the next distinct epoch rather than another bump to 24.

This review retains two independent actionable findings:

  1. Host session admission can still persist sessions targeting a retired connection when its credential remains stored.
  2. The authorization-code/paste-code OAuth path has lost its last production producer but remains across protocol, Host, runtime, and Desktop layers.

The Host admission gap independently warrants changes. The authorization-code comment intentionally covers the full producerless demand chain, extending beyond the narrower unused-#exchangeCode observation in the earlier review.

简体中文

建议 Request changes。

更正:compatibility epoch 问题已经由 Astro-Han 的顶层 review comment先行报告,因此我删除了自己的重复 inline comment。该评论对当前 main 的协调也更准确:main 已经使用 epoch 24,所以本 PR 需要下一个独立 epoch,而不是再次提升到 24。

本 review 保留两项独立且可执行的 finding:

  1. 当旧 credential 仍被保留时,Host session admission 仍能持久化指向退役 connection 的 session。
  2. authorization-code/paste-code OAuth 路径已经失去最后一个生产 producer,却仍横跨 protocol、Host、runtime 和 Desktop 层存在。

Host admission 缺口本身足以支持 Request changes。authorization-code 评论针对的是完整的无 producer demand chain,范围超过先前 review 中较窄的 unused #exchangeCode 观察。

Comment thread packages/core/src/provider-auth.ts
Comment thread packages/runtime-host/src/server/oauth-coordinator.ts

@hqhq1025 hqhq1025 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.

对抗性复核结论:当前 revision 不可合并。移除 Claude subscription OAuth 的方向正确,删除量也总体干净,但执行准入和协议边界还没有闭合。

  1. P1:breaking protocol 没有分配新的 compatibility epoch。

PR head 的 packages/runtime-host/src/protocol/index.ts:74 仍是 epoch 23;当前 main 已是 24。这个 PR 又删除了 OAuth provider/operation,所以合并结果会继续使用 24,epoch-24 的旧 Client/Host 能通过握手,再在 removed operation/provider 上中途失败。这里需要占用一个新的独立 epoch,并与其它并行 breaking PR 协调,不能依赖别的 PR 的 bump。

  1. P1:保留的旧 credential 会让 retired connection 通过 Host session admission。

packages/core/src/provider-auth.ts:93-100 对 retired provider 仍返回 requiresSecret: truepackages/storage/src/runtime-policy/coordinator.ts:562-587 只据此准备 credential,存在旧 token 时返回 readypackages/runtime-host/src/server/session-catalog-coordinator.ts:653-679 信任该结果,所以 default/explicit session.create 或 configuration update 可以持久化指向 retired Claude connection 的 Session,真正执行时才在更深层失败。

请把 retired/unavailable 作为 execution resolver 的一等结果,在 create/update commit 前拒绝,并覆盖 retained credential 下的 default 与 explicit target。

  1. P2:升级前的 retired Claude default target 没有释放。

packages/storage/src/runtime-policy/connection-catalog-document.ts:127-135 只清理旧 Gemini preview;Claude connection 保留后,其 defaultTarget 也原样保留。session-catalog-coordinator.ts:716-752 会继续选择它;apps/desktop/src/renderer/settings/providers-panel.tsx:266-270 还会显示“默认”,但不给用户切换该连接的操作。即使另有健康连接,新 Session 默认仍落到退役目标。迁移/读取时应释放 retired default,或提供等价的统一修复路径。

  1. P2:request_authorization_code / paste-code OAuth 链路已无生产 producer,却仍横跨 protocol、Host、runtime 和 Desktop 存在。

当前 Codex 与 xAI 都只产生 open_external,但 packages/runtime-host/src/protocol/oauth.ts:53-74server/oauth-coordinator.ts:69,124,452-476、Desktop pending-code/IPC、client presenter 以及 packages/runtime/src/oauth-login.ts:139 的 authorization-code exchange 仍被保留。应端到端删除这条无需求的敏感鉴权表面;未来有真实 provider 需要时再随 producer 引入。

对应地,apps/desktop/src/main/__tests__/runtime-host-oauth-ipc-main.test.ts:90 把 Codex fixture 伪造成 paste-code 流程,未覆盖真实 open_external 行为,应替换而不是继续维持死接口。

结论:方案目前不最优;需要局部根治,不需要推翻 retained-but-retired registry 设计。可删除完整 paste-code 链路、无调用的 exchange 注入、废弃 Claude env fixture 和重复 union member。CI 全绿,但没有覆盖 retained credential 的 Session admission、retired default migration 和合并后 epoch;这些是 merge blockers。

@Joob1n

Joob1n commented Aug 18, 2026

Copy link
Copy Markdown
Contributor Author

@M4n5ter @hqhq1025 — on the request_authorization_code teardown you both raised: I agree it should go, and I am doing it as the immediate follow-up rather than inside this PR. Reasoning first, then the exact scope so it is a commitment you can hold me to rather than a promise to clean up later.

Why split. This PR's claim is "a provider that violates Anthropic's Consumer Terms no longer exists." The paste-code teardown is cleanup that removal enables — it is producerless because of this PR — but it is not needed for that claim to hold, and nothing in the retirement is incorrect while the dead path exists. Verifying them together also means one reviewer has to check both "the provider is gone from seven authorities" and "codex and xai device login still work after an OAuth-presentation teardown," which are unrelated failure modes. It matters for revert granularity too: if the teardown breaks a device flow in a way the tests miss, reverting it should not drag the Consumer Terms fix back with it. This PR is already 73 files; the teardown adds 14 across four packages.

I am taking the cost of that honestly: the teardown is another wire change and will need its own epoch, so splitting spends two epochs instead of one.

PR B scope — exactly this, nothing else.

Removed end to end:

  • protocol/oauth.ts — the request_authorization_code member of OAuthPresentationMethod, its request variant, the authorization_code result variant, and their decoders
  • client/oauth-presentation.ts — the request_authorization_code case and the requestAuthorizationCode backend hook
  • Desktop runtime-host-oauth-presentation.tsrequestAuthorizationCode, submitAuthorizationCode, and the pending-code state they drive
  • Desktop runtime-host-oauth-ipc-main.ts — the complete-authorization channel and handler
  • server/oauth-coordinator.ts#exchangeCode, the exchangeCode input, the exchangeOAuthAuthorizationCode import, and the exchange/timeout machinery around them. This is the root-cause removal @M4n5ter asked for, which is why I have deliberately not deleted the unused #exchangeCode field in this PR — deleting the field alone is the shallow fix you warned against
  • runtime/oauth-login.tsexchangeOAuthAuthorizationCode, buildOAuthLoginAuthorization, pkceChallengeFromVerifier, assertPkceVerifier and the PKCE/state helpers only they use. Checked: buildOAuthLoginAuthorization has no production caller either — xai is a device flow despite its contract naming a loopback presentation, so the whole code-flow half of this module is producerless
  • core/oauth-subscription.tsparsePastedAuthorization, constantTimeStringEqual, PENDING_AUTHORIZATION_TTL_MS and the paste-code doc comments, once the above stop importing them
  • the tests that exist only to cover the removed path

Kept, because the live device flows import them: requestOAuthEndpointJson, requestOAuthTokenEndpointJson, decodeOAuthInitialTokenPayload, OAUTH_LOGIN_MAX_TOKEN_CHARS, OAuthTokenEndpointError. Verified against the import lists in codex-oauth-enrollment.ts and xai-oauth-enrollment.ts.

Also in PR B: runtime-host-oauth-ipc-main.test.ts:90, which fakes the Codex fixture as a paste-code flow. @hqhq1025 is right that it should be replaced rather than kept alive against a dead interface — but replacing it means asserting open_external, which is the presentation the teardown leaves as the only one, so it belongs with the teardown rather than ahead of it.

Everything else you both raised — the epoch, Host session admission with a retained credential, the retired default target, the override-ordering bypass @coderabbitai found, and the MAKA_CLAUDE_SUBSCRIPTION_EXPERIMENTAL fixture — I am fixing in this PR. Detail in a follow-up comment once they are pushed.

If either of you would rather have the teardown here despite the above, say so and I will fold it in.

@coderabbitai

coderabbitai Bot commented Aug 18, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

@Joob1n The split is reasonable.

The current PR can remain focused on retiring claude-subscription. PR B has a clear end-to-end removal scope for the producerless request_authorization_code path. Separate review and revert scope are valid reasons to split it.

For PR B, remove the wire variants, desktop IPC channel, coordinator exchange path, runtime authorization-code helpers, and path-only tests in the same change. Keep the device-flow helpers that Codex and xAI still import.

Please open a tracked follow-up issue for PR B, or link the follow-up PR here when it exists. This will make the remaining protocol cleanup explicit and reviewable.

I will review the current-PR fixes after they are pushed.

You are interacting with an AI system.

@Joob1n
Joob1n force-pushed the feat/retire-claude-subscription-oauth branch from 1d978df to 91c504e Compare August 18, 2026 11:20

@coderabbitai coderabbitai 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.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: e1ff2d6a-840f-468f-a678-dadabaee4355

📥 Commits

Reviewing files that changed from the base of the PR and between 1d978df and 91c504e.

📒 Files selected for processing (14)
  • apps/desktop/src/main/__tests__/runtime-host-oauth-ipc-main.test.ts
  • apps/desktop/src/main/runtime-host-client.ts
  • apps/desktop/src/renderer/settings/providers-panel.tsx
  • packages/runtime-host/src/__tests__/session-catalog-coordinator.test.ts
  • packages/runtime-host/src/protocol/index.ts
  • packages/runtime-host/src/server/session-catalog-coordinator.ts
  • packages/runtime/src/__tests__/claude-subscription-runtime.test.ts
  • packages/runtime/src/__tests__/oauth-login.test.ts
  • packages/runtime/src/__tests__/provider-contract-matrix.ts
  • packages/runtime/src/model-runtime.ts
  • packages/storage/src/__tests__/runtime-policy-stores.test.ts
  • packages/storage/src/runtime-policy/connection-catalog-document.ts
  • packages/storage/src/runtime-policy/coordinator.ts
  • packages/storage/src/runtime-policy/operations.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • apps/desktop/src/main/runtime-host-client.ts
  • packages/runtime/src/tests/claude-subscription-runtime.test.ts
  • apps/desktop/src/renderer/settings/providers-panel.tsx
  • apps/desktop/src/main/tests/runtime-host-oauth-ipc-main.test.ts

Included review availability: Your plan includes up to 3 reviews per rolling hour; 2 remain after this review.

Comment thread packages/storage/src/runtime-policy/connection-catalog-document.ts
@Joob1n

Joob1n commented Aug 18, 2026

Copy link
Copy Markdown
Contributor Author

Pushed. Point by point, and where I was wrong I have said so rather than quietly fixed it.

@Astro-Han / @hqhq1025 — P1/P2, the epoch. Fixed: 24 → 25 in protocol/index.ts, and this time I checked git diff origin/main -- protocol/index.ts actually shows the line before claiming it. The earlier claim was a 22 → 23 bump that a rebase turned into a no-op while the description kept asserting it; the "old clients are rejected by the gate" sentence was therefore describing a coincidence, exactly as you said. The description now states the bump this PR makes and why it takes its own rather than riding #3145's.

@M4n5ter / @hqhq1025 — P1/P2, Host session admission with a retained credential. Fixed, and you were right that my earlier reasoning was too convenient — I had considered guarding resolveExecutionConnection and talked myself out of it as "protocol churn", which ignored that Bot, CLI and scheduled runs never pass the Desktop readiness gate.

ResolveExecutionConnectionResult gains provider_retired as a first-class result rather than reusing an existing kind: disabled is a user choice they can undo, credential_not_configured is repaired by signing in, and a retired connection is neither — it holds a working credential and can still never execute, so folding it into either would make callers give wrong advice. SessionCatalogCoordinator refuses on that result before the Session is committed. Regressions cover both the default target and an explicit one; reverting the admission check turns both red, reverting the resolver check turns the storage case red.

@hqhq1025 — P2, the retired default target. Fixed, and thank you for catching the second half of it, which was mine: I had hidden "set as default" for retired connections but left the branch order such that a connection which already was the default still rendered the 默认 Badge with no control to move it — I removed a dead end and added a new one. Loading the catalog now releases a defaultTarget pointing at a retained retired connection (the existing retiredConnectionIds mechanism, which previously only covered fully-removed Gemini rows), and the panel checks retirement before either default state.

@coderabbitai — Major, the override-ordering bypass. Fixed: resolveModelRuntime refuses a retired provider before lookupModelProviderOverride, so an override cannot build an active adapter and skip the refusal. To be accurate about severity: no override for claude-subscription exists today — I checked every model id in its fallback list — so this was latent, not live. It is still worth closing because that table is generated from an external source and one row should not be able to undo a retirement.

@coderabbitai — the contract matrix and the Codex IPC fixture. Both fixed here. wireDimensionCell and the reasoning-replay cell now classify an unavailable adapter as not-applicable instead of falling through to a generated anthropic-messages contract. The desktop fixture now drives openExternal rather than requestAuthorizationCode@hqhq1025, you asked for this to be replaced rather than kept alive against a dead interface, and on reflection you were right that it does not need to wait for the teardown, since open_external is the live presentation today. I said in my earlier comment that it belonged with PR B; changing that here rather than silently.

@Astro-Han — P3, the mutation table overstated one row. Correct, and now stated in the description: reverting only the storage interactive-login allow list leaves the suite green, because the auth-contract gate independently produces the same refusal. That is defense in depth working, but the row claimed discriminating power it does not have.

@Astro-Han — P3, "impossible to unregister" was overstated. Also corrected in the description. isRetiredGeminiCliConnection is the precedent and unregistering is possible; keeping the entry is a product choice — the user has a credential on this machine and the connection is what makes it visible and deletable — not a technical necessity.

Also in this push: oauth-login.test.ts no longer sets the removed MAKA_CLAUDE_SUBSCRIPTION_EXPERIMENTAL flag; nothing in the tree references it now.

Still deliberately not done here: the #exchangeCode field stays, because @M4n5ter asked for the root-cause removal rather than a field deletion, and it dies with the paste-code teardown in the follow-up PR whose scope is in the comment linked above.

Not covered: the new retired UI states have no Storybook stories and I have not run the Playwright E2E suite; both are stated in the description rather than left implied.

Before/after screenshots for the UI/UX gate are in the PR body.

@coderabbitai

coderabbitai Bot commented Aug 18, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '=== Changed files relevant to reported fixes ==='
git diff --name-only origin/main...HEAD | rg \
  '(^packages/runtime-host/src/protocol/index\.ts$|^packages/storage/src/runtime-policy/|^packages/runtime/src/model-runtime\.ts$|^packages/runtime/src/__tests__/provider-contract-matrix\.ts$|^apps/desktop/src/main/__tests__/runtime-host-oauth-ipc-main\.test\.ts$|^packages/runtime-host/src/server/session-catalog-coordinator\.ts$|^packages/runtime-host/src/__tests__/session-catalog-coordinator\.test\.ts$|^packages/storage/src/__tests__/runtime-policy-stores\.test\.ts$|^packages/storage/src/runtime-policy/connection-catalog-document\.ts$|^apps/desktop/src/renderer/settings/providers-panel\.tsx$|^packages/runtime/src/__tests__/claude-subscription-runtime\.test\.ts$|^packages/runtime/src/__tests__/oauth-login\.test\.ts$)' || true

echo '=== Targeted outlines ==='
ast-grep outline packages/runtime/src/model-runtime.ts --items all --view expanded
ast-grep outline packages/storage/src/runtime-policy/coordinator.ts --items all --view expanded
ast-grep outline packages/runtime-host/src/server/session-catalog-coordinator.ts --items all --view expanded

echo '=== Epoch and retirement enforcement references ==='
rg -n -C 4 \
  'RUNTIME_HOST_COMPATIBILITY_EPOCH|provider_retired|isRetiredProvider|lookupModelProviderOverride|wireDimensionCell|reasoning.*[Rr]eplay|openExternal|requestAuthorizationCode|MAKA_CLAUDE_SUBSCRIPTION_EXPERIMENTAL' \
  packages/runtime-host/src/protocol/index.ts \
  packages/runtime/src/model-runtime.ts \
  packages/runtime/src/__tests__/provider-contract-matrix.ts \
  packages/storage/src/runtime-policy \
  packages/storage/src/__tests__/runtime-policy-stores.test.ts \
  packages/runtime-host/src/server/session-catalog-coordinator.ts \
  packages/runtime-host/src/__tests__/session-catalog-coordinator.test.ts \
  apps/desktop/src/main/__tests__/runtime-host-oauth-ipc-main.test.ts \
  apps/desktop/src/renderer/settings/providers-panel.tsx \
  packages/runtime/src/__tests__/claude-subscription-runtime.test.ts \
  packages/runtime/src/__tests__/oauth-login.test.ts || true

echo '=== Remaining removed experimental flag references ==='
rg -n -C 2 'MAKA_CLAUDE_SUBSCRIPTION_EXPERIMENTAL' . -g '!node_modules' -g '!dist' -g '!build' || true

echo '=== Exact diff for relevant files ==='
git diff --unified=35 origin/main...HEAD -- \
  packages/runtime-host/src/protocol/index.ts \
  packages/runtime/src/model-runtime.ts \
  packages/runtime/src/__tests__/provider-contract-matrix.ts \
  packages/storage/src/runtime-policy/coordinator.ts \
  packages/storage/src/runtime-policy/operations.ts \
  packages/storage/src/runtime-policy/connection-catalog-document.ts \
  packages/storage/src/__tests__/runtime-policy-stores.test.ts \
  packages/runtime-host/src/server/session-catalog-coordinator.ts \
  packages/runtime-host/src/__tests__/session-catalog-coordinator.test.ts \
  apps/desktop/src/main/__tests__/runtime-host-oauth-ipc-main.test.ts \
  apps/desktop/src/renderer/settings/providers-panel.tsx \
  packages/runtime/src/__tests__/claude-subscription-runtime.test.ts \
  packages/runtime/src/__tests__/oauth-login.test.ts

Length of output: 156942


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '=== PR body ==='
gh pr view 3183 --repo maka-agent/maka-agent --json body,url --jq '"URL: \(.url)\n\n\(.body)"'

echo '=== Candidate tracked follow-ups ==='
gh search issues \
  --repo maka-agent/maka-agent \
  --state open \
  --limit 30 \
  --json number,title,url,body \
  --jq '.[] | select(
    ((.title + "\n" + .body) | test(
      "request_authorization_code|complete-authorization|exchangeOAuthAuthorizationCode|paste-code";
      "i"
    ))
  ) | "#\(.number) \(.title)\n\(.url)"' || true

Length of output: 8507


@Joob1n The amended implementation addresses the reported findings.

  • The compatibility epoch is now 25.
  • Storage returns provider_retired before credential resolution.
  • Session admission rejects retired default and explicit targets before persistence.
  • Runtime resolution rejects retired providers before model overrides.
  • Catalog loading clears retired default targets.
  • Settings suppresses the retired default badge and action.
  • The contract matrix marks unavailable adapters as not applicable.
  • The Codex fixture uses openExternal.
  • No MAKA_CLAUDE_SUBSCRIPTION_EXPERIMENTAL reference remains.

The Comment image satisfies the before/after UI screenshot requirement. It shows the retired composer state, model-list status, and connection-detail banner with unavailable actions removed.

One follow-up remains: the PR body links the teardown scope comment, but not a tracked issue or follow-up PR for request_authorization_code. Please create and link that follow-up before merge.


✏️ Learnings added
Learnt from: Joob1n
URL: https://github.com/maka-agent/maka-agent/pull/3183

Timestamp: 2026-08-18T11:56:10.342Z
Learning: For the TypeScript provider registry, `claude-subscription` remains registered with `retired: true` so stored connections and their local credentials stay visible and deletable. Retired providers must not be executable, selectable as defaults, or available for OAuth login.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

You are interacting with an AI system.

@Astro-Han Astro-Han 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.

Thanks for taking on the retirement as an end-to-end product change. The core direction is sound: keep the provider identity only so existing rows remain decodable and deletable, remove its OAuth/runtime implementation, and fail closed at execution. Four independent reviewer passes plus a read-only DeepSeek V4 Flash high-effort pass converged on the remaining boundary gap below.

The first-principles invariant should be: a retired provider may be decoded and deleted, but it cannot enter any ordinary mutation or action path. Today the read path clears a retired default and the execution path rejects it, while the catalog mutation API can still create a new claude-subscription row or commit it as a default because the provider remains a valid ProviderType; enabled retired rows can also still surface generic test actions. That creates states which a later read silently rewrites and actions which are guaranteed to fail.

The smallest coherent fix is to enforce retirement once at the storage mutation authority (create, setDefaultTarget, and any update that would make the row executable), then expose the same capability to shared action construction so test/default actions are omitted. Please keep decode/delete as the explicit exceptions. This is simpler than adding more renderer-specific guards and makes the retained registry entry serve exactly one compatibility purpose.

I did not run a local test suite; this review is based on the latest head, focused static tracing, the existing CI results, four independent @reviewer reviews, and a read-only ollama-cloud/deepseek-v4-flash high-effort review. AI-assisted review; I verified the reported control flow and discarded findings that were only artifacts of comparing this old branch head directly with current main.

中文评论

感谢把 Claude subscription 的退役做成端到端收口。核心方向是对的:只保留 provider 身份用于解码和删除旧连接,移除 OAuth/Runtime 实现,并在执行边界 fail closed。

剩余问题是 authority 没有完全统一:退休 provider 仍是合法 ProviderType,所以 mutation API 仍可新建该连接、把它设为默认,通用 action 层也仍可能提供测试入口;但读取时又会清空默认值,执行时必然拒绝。这会制造“写入成功、读回被改写”以及必然失败的操作。

更符合第一性原理和奥卡姆剃刀的方案,是在 storage mutation authority 一次性禁止退休 provider 的新建、设默认和重新启用,并让共享 action 构造消费同一 capability;仅把解码和删除保留为明确例外。这样不需要继续在各个 renderer 补零散判断。

本次未运行本地测试;结论来自最新 head 的静态追踪、现有 CI、四个独立 reviewer 和一次只读的 DeepSeek V4 Flash high-effort 审查。AI 辅助审查;我已人工复核控制流,并剔除了由旧分支与当前 main 直接比较产生的误报。

protocol: 'anthropic',
runtimeAdapter: { kind: 'claude-subscription' },
runtimeAdapter: { kind: 'unavailable' },
retired: true,

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.

P2 — Reject retired providers at the catalog mutation authority. Keeping this registry entry is necessary for decoding existing rows, but it also keeps claude-subscription valid for CreateCatalogConnectionInput. ConnectionCatalogDocumentOwner.create() does not check isRetiredProvider, so an IPC/protocol caller can still create a brand-new enabled connection that can never execute. The same gap lets setDefaultTarget() return committed for a retired row, after which read() silently normalizes that default back to null. Please make decode/delete the explicit exceptions and reject create/default/re-enable mutations for retired providers in the storage owner; add focused tests for create and set-default rejection.

@Joob1n
Joob1n force-pushed the feat/retire-claude-subscription-oauth branch from ace2ca2 to 330cc83 Compare August 18, 2026 13:32
Maka could sign in with a Claude Pro/Max subscription and send inference
through it. Anthropic's Consumer Terms permit programmatic access to the
consumer Claude services only through an API key or explicit permission,
and this path had neither: it presented itself as Claude Code — borrowing
that client_id, its User-Agent, its beta header set and an `x-app: cli`
marker — to get requests accepted. The account carrying that risk is the
user's, not Maka's.

Remove the capability rather than gate it. `claude-subscription` keeps its
registry entry so a stored connection still decodes and renders, and is
marked `retired`, which is distinct from a provider that was never wired:
both have no Runtime adapter, but only one used to work.

Retirement is refused at each authority that could otherwise admit the
connection, so no single revert makes it sendable again:

- the auth contract hides every action, which is what makes the storage
  layer refuse a model fetch or a connection test
- the readiness gate reports `provider_retired` before the send is
  admitted, instead of letting it fail inside model construction
- the model catalog resolves every model to `provider_removed`, so the
  pickers stop offering them
- the interactive-login allow list and the Host wire enum no longer name it
- `getAIModel` and `resolveModelRuntime` throw as the last backstop

Settings explains the state instead of pointing at a sign-in that no longer
exists, and stops offering "set as default" and "test connection" for a
connection that cannot perform either. Deleting the connection is what
clears the credential this machine still holds.

The impersonation code goes with it: the cloaked request builder, the
Claude token endpoint and its client identity, the cloaked model-fetch
headers, and the subscription usage/quota path that needed that same
identity to read.

`RUNTIME_HOST_COMPATIBILITY_EPOCH` moves to 23: the OAuth login provider
enum and the account-usage operation both changed.

Generated-by: Claude Code
@Joob1n
Joob1n force-pushed the feat/retire-claude-subscription-oauth branch from 330cc83 to 66f94be Compare August 18, 2026 13:33

@Astro-Han Astro-Han 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.

The latest head closes the prior retirement gaps cleanly. The compatibility epoch is now 26; legacy rows remain decodable/deletable but cannot be created, enabled, or selected as defaults; default targets pointing at retired connections are released on read; Host admission rejects provider_retired before credentials or Session persistence; and the auth/model/runtime backstops prevent an override bypass. Current-main integration is clean and all live checks are green.

From first principles, provider retirement now has one enforceable storage/execution contract rather than relying on UI removal. The remaining producerless request_authorization_code / paste-code path can be deleted as a separate, tracked cleanup; it is no longer reachable through a live provider and does not block this retirement. Please also update the PR description's epoch note from 24→25 to the actual 25→26.

Approved. Reviewed with Codex using two independent reviewer agents and an external DeepSeek review; I verified the latest head, mutation/admission/model backstops, compatibility epoch, current-main merge, and live CI.

中文

最新 head 已完整闭合此前的退休缺口:compatibility epoch 现为 26;旧行仍可解码/删除,但不能新建、启用或设为默认;读取时会释放指向退休连接的默认目标;Host admission 在读取凭据或持久化 Session 前返回 provider_retired;auth/model/runtime backstop 也阻止 override 绕过。与当前 main 可干净合并,实时检查全绿。

按第一性原理,provider retirement 现在由统一的存储/执行契约强制,而不是依赖 UI 删除。剩余没有 producer 的 request_authorization_code / paste-code 路径可以作为独立、可跟踪的清理删除;它已无法通过现有 provider 到达,不阻塞本次退休。另请把 PR 描述中的 epoch 从 24→25 修正为实际的 25→26。

批准。本次由 Codex 配合两个独立 reviewer agent 和外部 DeepSeek 审查;我核验了最新 head、mutation/admission/model backstop、compatibility epoch、与当前 main 的合并和实时 CI。

@likun666661

Copy link
Copy Markdown
Member

复核最新 head 66f94be 后,核心退役链路已经基本闭合:retained credential 不再让 execution resolver 返回 ready,default/explicit Session admission 都会在 commit 前拒绝,retired default 会被释放,create/default mutation 也已收口;当前 CI 全绿。

还剩两个合并前建议修正的点:

  1. P2 — Command Palette 仍暴露必然失败的退休连接操作。 buildCommandPaletteCommands 只按 connection.enabled 过滤连接,所以升级后保留且仍为 enabled 的 claude-subscription 行仍会生成“设为默认”和“测试连接”命令(apps/desktop/src/renderer/command-palette-commands.ts:419-468)。前者会被新的 storage default-target gate 拒绝,后者会被 hidden auth action 拒绝;Settings 已经隐藏了这些操作,但命令面板仍提供死入口。请让共享 command construction 使用 retired/action capability 过滤,并补一个 retained-enabled connection 的回归。

  2. P3 — PR 描述中的 epoch 已再次落后于最新 rebase。 当前 diff 实际是 RUNTIME_HOST_COMPATIBILITY_EPOCH 25 → 26,描述仍写 24 → 25。实现是正确的,但发布/并行 breaking PR 协调依赖这里的事实,请同步更新描述。

request_authorization_code teardown 拆成后续 PR 的理由可以接受;建议把后续工作落成可追踪的 issue/PR,而不只保留评论承诺。

The command palette filtered per-connection commands on `enabled` alone,
so a retained retired connection — which stays enabled so its credential
remains visible and deletable — still offered "set as default" and
"test connection", both refused downstream by the storage default-target
gate and the hidden auth actions. The palette now checks the same
isRetiredProvider capability Settings uses, including the diag
test-default entry for a stale in-memory default. Regression covers a
retained-enabled retired row next to a live positive control; reverting
the loop filter turns it red.

Generated-by: Claude Code
Claude-Session: https://claude.ai/code/session_014ajaRxC4jydavY9nYUFj5J
@Joob1n

Joob1n commented Aug 18, 2026

Copy link
Copy Markdown
Contributor Author

@likun666661 — both fixed in 6fc4447, and the teardown is now tracked.

P2, the palette. You were right, and it was the one shared surface the sweep missed: buildCommandList filtered on enabled alone, and a retained retired connection stays enabled by design — the row exists so the credential remains visible and deletable. It now checks the same isRetiredProvider capability Settings uses, in the per-connection loop and in the diag:test-default entry (the catalog releases a retired default on load, so that one guards the window where the renderer still holds the stale slug). The regression covers exactly the case you named — a retained, still-enabled retired row — next to a live non-default connection as the positive control, so an over-broad filter cannot pass it by dropping everything; reverting the loop filter alone turns it red. Desktop suite: 927/927.

P3, the epoch note. The description now states the actual diff, 25 → 26, verified against git diff origin/main before writing it this time.

The teardown promise is now #3219 rather than a comment: full file-by-file scope, what stays because the live device flows import it, and the epoch cost of the split.

@M4n5ter @hqhq1025 — your review requests are addressed as of this head (Host admission with a retained credential, the retired default target, the storage mutation gates, and the fixture replacement), and @Astro-Han has approved the retirement chain. Could you take another look when you have a moment?

Astro-Han
Astro-Han previously approved these changes Aug 18, 2026

@Astro-Han Astro-Han 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.

The latest delta closes the remaining Command Palette action surface for retained retired providers. It reuses the existing retirement authority, covers all three connection command paths, and includes a discriminating live-connection positive control. I found no new P0–P3 issue in this head.

The prior epoch, Host admission, retired-default, mutation-authority, and runtime/auth backstop findings remain resolved. The producerless authorization-code path is now explicitly tracked as separate follow-up work.

Reviewed with Codex using two independent review passes and DeepSeek V4 Flash as an external adversarial pass; I verified the latest delta and prior-finding state against this exact head and current main.

中文

最新 delta 已关闭 retained retired provider 在 Command Palette 中最后的 action surface。实现复用了现有 retirement authority,覆盖三类 connection command,并用 live connection 正向对照避免过度过滤。当前 head 未发现新的 P0–P3。

此前的 epoch、Host admission、retired default、mutation authority 和 runtime/auth backstop 问题仍保持已解决;producerless authorization-code 路径已明确拆为后续工作。

本次由 Codex 两轮独立审查,并使用 DeepSeek V4 Flash 做外部对抗审查;我核对了当前 head、最新 delta 与 prior findings。

@M4n5ter M4n5ter left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

English

Requesting changes on the latest head.

The earlier epoch, default-target, general session-admission, mutation-authority, runtime-backstop, and Command Palette findings remain fixed. This pass found three non-duplicate residual paths:

  1. Removing oauth.account.usage.fetch makes previously issued Remote Host access files fail decoding, preventing the Host from starting. The known Remote Host population is almost nonexistent, so its practical reach is negligible, but the upgrade failure is deterministic for affected profiles.
  2. Configured subagent admission still considers retained retired connections available. Direct spawn and graph provisioning can consequently persist a child/operator that can never execute.
  3. The CLI /model projection still presents models belonging to the retired connection and only rejects them after selection.

The configured-subagent persistence gap independently warrants changes. Both remaining selection gaps can be closed by consuming the new canonical isRetiredProvider() authority rather than adding renderer- or caller-specific state.

The simplify audit found no additional merge blocker. The producerless authorization-code/paste-code path is already tracked by an existing review and is intentionally not duplicated here. Making retired connections fully read-only tombstones would be a reasonable follow-up simplification.

I verified these paths against head 6fc4447. Relevant targeted suites pass and all current GitHub checks are green; the missing retired-provider cases are not covered by those tests.

I’ll respond quickly to follow-ups on this PR, so please feel free to ping me directly.

简体中文

建议在最新 head 上 Request changes。

此前关于 compatibility epoch、default target、通用 session admission、mutation authority、runtime backstop 和 Command Palette 的问题仍保持已修复。本轮发现三个未与现有 review 重复的剩余入口:

  1. 删除 oauth.account.usage.fetch 会使此前签发的 Remote Host access file 解码失败,阻止 Host 启动。已知 Remote Host 用户几乎不存在,实际影响面可以忽略不计,但对命中的 profile 来说升级故障是确定性的。
  2. Configured subagent admission 仍把保留的 retired connection 视为可用,因此直接 spawn 和 graph provisioning 可以持久化一个永远无法执行的 child/operator。
  3. CLI /model 投影仍会展示属于 retired connection 的模型,直到用户选择后才拒绝。

Configured-subagent 的持久化缺口本身足以支持 Request changes。两个剩余选择入口都可以通过消费新的统一 isRetiredProvider() authority 来关闭,不需要增加 renderer 或 caller 专属状态。

简化审计没有发现额外 merge blocker。没有 producer 的 authorization-code/paste-code 路径已经由现有 review 跟踪,本次不重复评论。将 retired connection 进一步收敛为完全只读的 tombstone,可以作为后续简化。

我在 head 6fc4447 上验证了这些路径。相关 targeted suites 通过,当前 GitHub checks 全绿;但现有测试没有覆盖上述 retired-provider 场景。

对于这个 PR,我会尽快回复后续讨论;有需要请直接 ping 我。

Comment thread packages/runtime-host/src/protocol/operations.ts
Comment thread packages/core/src/provider-registry.ts
The list badge said provider_retired, but the editor still judged a
connection by enabled alone in usableConnections, validConnection, and
connectionOptions, so a retained retired row could be selected and
saved into a preset the runtime admission is guaranteed to refuse. One
predicate — isSelectableSubagentConnection, enabled and not retired —
now feeds all of them plus the model derivations, and the option row
for an enabled-but-retired connection says why it cannot be picked
instead of graying out silently.

Generated-by: Claude Code
Claude-Session: https://claude.ai/code/session_014ajaRxC4jydavY9nYUFj5J
@Joob1n

Joob1n commented Aug 19, 2026

Copy link
Copy Markdown
Contributor Author

@Astro-Han — the editor projection is closed in 985802b. One predicate, isSelectableSubagentConnection (enabled and not retired), now feeds usableConnections, validConnection, connectionOptions, and both model derivations, so the editor can no longer save a preset the runtime admission refuses as provider_retired. The option row for an enabled-but-retired connection says why it cannot be picked (登录方式已移除 · 请改用其他连接) instead of graying out silently — same pattern as the vanished-connection placeholder beside it. The predicate has direct regressions (retired-enabled, retired-disabled, live-enabled, live-disabled) in the presentation tests; desktop typecheck and suite green.

@Astro-Han Astro-Han 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.

The latest head correctly closes the Desktop Subagent-editor gap: one isSelectableSubagentConnection() predicate now excludes retired providers from usable models, validity, and options, with focused retained/disabled/live coverage.

The existing catalog-mutation authority thread is still only partially closed. Create/default/session admission now reject retired providers, but connection.catalog.update can take a retained disabled claude-subscription row and commit enabled: true; runtime then predictably rejects it as provider_retired. I am not duplicating that inline thread. The smallest coherent fix is to reject re-enabling retired rows at the storage mutation authority (or reject all ordinary updates if retained rows are decode/delete-only), with no revision change on rejection.

All live checks are green, but merge state remains blocked by unresolved review state.

Reviewed with Codex using two independent reviewer agents and OpenCode Go DeepSeek V4 Flash (high); I verified the exact delta, the prior Desktop finding, storage/runtime retirement invariants, focused tests, and live CI.

中文

最新 head 已正确修复 Desktop Subagent editor 缺口:统一的 isSelectableSubagentConnection() predicate 会在 usable models、validity 和 options 中排除 retired provider,并有 retained/disabled/live 聚焦覆盖。

但现有 catalog mutation authority 线程仍只部分关闭。create/default/session admission 已拒绝 retired provider;connection.catalog.update 仍可把 retained disabled claude-subscription row 提交为 enabled: true,随后 runtime 必然以 provider_retired 拒绝。我不重复发布该行内线程。最小一致修复是在 storage mutation authority 拒绝重新启用 retired row;若 retained row 只用于 decode/delete,则可直接拒绝所有普通 update,并保证 rejection 不增加 revision。

实时检查全绿,但 unresolved review state 仍使 merge state blocked。

本次由 Codex 配合两个独立 reviewer agent,以及 OpenCode Go DeepSeek V4 Flash(high)审查;我核验了精确增量、既有 Desktop finding、storage/runtime retirement invariant、聚焦测试和实时 CI。

M4n5ter
M4n5ter previously approved these changes Aug 19, 2026

@M4n5ter M4n5ter left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

English

LGTM. The latest update closes the remaining subagent editor path, and the previously reported findings are resolved. All current checks pass.

简体中文

LGTM。最新更新已关闭剩余的 subagent editor 路径,之前提出的问题均已解决。当前检查全部通过。

@hqhq1025 hqhq1025 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.

One new merge-blocking finding is attached inline.

The earlier hqhq1025 blockers around the compatibility epoch, retained-credential Session admission, and retired default targets are fixed on this branch. I did not duplicate the existing unresolved retired-provider update/re-enable thread.

This exact head also conflicts with current main, whose Runtime Host compatibility epoch is already 28. The required rebase must allocate the next distinct epoch for this wire removal, preserve both sides' authority changes, and receive a complete new-head review.

I reviewed the complete revision; required checks are green, build:test passed, and 228 focused local tests passed.

Codex-assisted review performed under the maintainer-approved review workflow.

Comment thread packages/storage/src/runtime-policy/connection-catalog-document.ts
Config export carries every catalog connection, and this retirement
deliberately retains existing claude-subscription rows so the user can
see and delete them. A fresh-profile import saves connections one at a
time, so that row reached createConnection(), which refuses it — and the
refusal aborted the restore partway, after whichever live connections
had already committed and before settings, credentials, and memory. That
is the supported backup path for exactly the users this retirement
affects.

The storage guard stays: creating a retired connection is still refused,
because it could never execute. Import planning no longer hands it one —
the row is planned as skipped, and its credential is skipped with it,
since only a created or overwritten slug gets a secret written.

The regression restores a full bundle whose live connection is ordered
before the retained retired one, so an aborted import would look like a
partial success rather than a clean failure; reverting the filter turns
it red.

Reported by @hqhq1025.

Generated-by: Claude Code
Claude-Session: https://claude.ai/code/session_014ajaRxC4jydavY9nYUFj5J

@hqhq1025 hqhq1025 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.

The previous config-restore P1 is fixed on this revision. Retired-provider connections are removed from the import plan before any connection save, their credentials remain unapplied, and the rest of the selected bundle continues normally. Exact-head build:test, 149 focused retirement/import tests, lint, format, and the Astryx surface inventory all passed.

I am leaving a COMMENT rather than approving because both hosted GitHub Actions suites are currently action_required, and the existing current-code update/re-enable thread remains unresolved. I found no new unique finding and did not duplicate that thread.

Codex-assisted review performed under the maintainer-approved review workflow.

@Joob1n

Joob1n commented Aug 21, 2026

Copy link
Copy Markdown
Contributor Author

Could a committer approve the workflow runs on df59be7? Fork PRs need that click since the move into apache, and two runs are queued.

What changed since the last review, so the new head is not a black box:

@hqhq1025's P1 — config restore. You were right, and it was the worst kind of break: the users this retirement affects are exactly the ones whose backup carries a claude-subscription row, and the storage guard turned their restore into a partial one — connections saved up to that point, then nothing. Settings, credentials and memory never landed.

The guard stays, because creating a retired connection is still wrong. Import planning no longer hands it one: the row is planned as skipped, and its credential is skipped with it, since only a created or overwritten slug gets a secret written. The regression restores a full bundle whose live connection is ordered before the retained retired one — so a regression would look like a partial success rather than a clean failure — and reverting the filter turns it red.

The rebase you asked for. Current main is merged; nine conflicts, resolved individually rather than by taking a side:

  • chat-readiness.ts and its test: deleted on main by refactor(desktop): remove superseded local authority paths #3252, and our change to it was one copy string. Deletion accepted — the authoritative refusals live in isConnectionReady, resolveExecutionConnection and the Session admission, none of which moved.
  • claude-subscription-card.tsx: chore(ui): show signed-in mark in model provider list #3315 added an onLoginSuccess prop to the card this PR deletes. Deletion kept; provider-oauth-section.tsx takes main's shape with the claude branch removed, so the copilot panel keeps the new prop.
  • provider-registry.ts: both sides added a field, so both are kept.
  • connection-readiness.ts: main rewrote the ordering doc; the retirement rule is inserted at the position the code actually checks it, not appended.
  • provider-catalog-contract.test.ts: both sides added a describe block and both were cut mid-it by the conflict region, so ours is closed explicitly rather than borrowing main's closing braces.
  • astryx-surface-file-inventory.md: regenerated rather than hand-picked; 188 files.

Epoch is 30, not the 24→25 an earlier description claimed and not the 26 this branch carried before the rebase. Main reached 29 while this sat, and main also adopted a changelog comment format for that constant, which the new entry follows. Verified with git diff origin/main before writing this line, for reasons this PR's history explains.

Local state: build, typecheck, lint, format clean; the retirement contract suite is 9/9; the runtime suite's failing set is byte-identical to the same suite at the merge base on this machine (environmental ripgrep/PTY/filesystem cases), and eval's is the Docker preflight.

create() and setDefaultTarget() already refused a retired provider, but
update() did not — so a disabled retired connection could be re-enabled
and become a default candidate again, leaving every downstream refusal
as the only thing between it and a Session. That was the half of the
mutation-authority finding this branch had not closed.

The whole update is refused rather than the `enabled` field alone: a
retired connection has no edit worth committing, and a field-by-field
allow list would have to be revisited for every field added later. The
Desktop UI offers no enable toggle for a connection, so nothing legible
is being taken away. Read and delete stay the exceptions, and the
regression proves it — the refused row is still readable and still
deletable afterwards, which is the whole reason it is retained.

Reported by @Astro-Han; @hqhq1025 named the open thread.

Generated-by: Claude Code
Claude-Session: https://claude.ai/code/session_014ajaRxC4jydavY9nYUFj5J
@Joob1n

Joob1n commented Aug 21, 2026

Copy link
Copy Markdown
Contributor Author

@hqhq1025 — thank you for naming the open thread rather than re-deriving it; I had closed half of @Astro-Han's mutation-authority finding and not noticed. 46d9eff closes the rest.

create() and setDefaultTarget() refused a retired provider, but update() did not — so a disabled retired connection could be re-enabled, and from there it is a default candidate again with only the downstream refusals between it and a Session. The whole update is now refused rather than the enabled field alone: a retired connection has no edit worth committing, and a field-level allow list would need revisiting every time a field is added. Before choosing that I checked the Desktop side for an enable toggle on a connection — there is none, so no legible affordance is being removed.

Read and delete stay the exceptions, and the regression asserts exactly that: after the refused edit the row is still readable and still deletable, which is the entire reason this retirement keeps it. Reverting the guard turns that test red.

On your other reason for not approving: the workflow runs were approved and are green on the rebased head — the consolidated test job covers lint, format, build, typecheck, the Astryx surface and theme checks, knip, the affected workspace suites, Runtime Host, Desktop e2e, the alignment audit, Storybook, and the CLI release-candidate build and install. Locally the runtime suite's failing set is byte-identical to the same suite at the merge base on this machine, and @maka/storage is 48/48 with the new case.

For the record on approvals: @M4n5ter approved at 985802b, and that approval was dismissed automatically by the pushes that fixed your P1 and this thread — not withdrawn.

@Joob1n

Joob1n commented Aug 21, 2026

Copy link
Copy Markdown
Contributor Author

@M4n5ter — your approval at 985802b was dismissed automatically by the two pushes since, not withdrawn. Both were review fixes: @hqhq1025's config-restore P1 (a retained retired connection aborted a whole bundle restore) and the half of @Astro-Han's mutation-authority finding this branch had left open (update() could re-enable a retired connection). Nothing else changed except the merge with current main, whose conflicts are itemized above. Could you take another look when convenient?

@Joob1n
Joob1n requested a review from hqhq1025 August 21, 2026 05:48
likun666661
likun666661 previously approved these changes Aug 21, 2026
M4n5ter
M4n5ter previously approved these changes Aug 21, 2026

@M4n5ter M4n5ter left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

English

LGTM. The latest changes resolve the remaining catalog-update and config-restore issues, and I found no path that can restore authentication or execution for the retired provider.

Two user-facing inconsistencies remain:

  • The retained connection detail still shows model management and advanced request editing. Model and request-body changes are rejected by the storage boundary, while request headers can be saved but can never affect execution.
  • Because the retired default target is released during catalog loading, Health Center reports “Select a default model” instead of explaining that the provider has been retired.

Both are reachable for users with a legacy connection, but neither bypasses the storage, session-admission, OAuth, or runtime protections. I consider them non-blocking UI follow-ups. All current checks pass.

简体中文

LGTM。最新修改已解决剩余的 catalog update 和配置恢复问题,也没有发现能够恢复退休 provider 的认证或执行能力的路径。

目前仍有两处用户界面不一致:

  • 保留的旧连接详情页仍显示模型管理和高级请求编辑。模型与 request body 修改会被存储边界拒绝;request headers 虽然可以保存,但永远不会参与执行。
  • catalog 加载时会释放指向退休连接的默认目标,因此 Health Center 会提示“请选择默认模型”,而不是说明该 provider 已经退休。

拥有旧连接的用户确实可以遇到这些问题,但它们都无法绕过存储、Session admission、OAuth 或 Runtime 的保护。我认为可以作为非阻塞的 UI follow-up。当前检查全部通过。

@hqhq1025 hqhq1025 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.

Requesting changes on head 46d9efffe73220642c177d7d31c9cf7d27a61936.

Problem reconstruction

This PR correctly retires the Claude subscription OAuth path: it removes the Claude client identity and request-cloaking implementation, keeps the provider registered only so existing rows remain decodable and deletable, and blocks auth, defaults, selection, Session admission, model resolution, testing, CLI choices, and subagent execution. The problem definition is correct, and the retained-tombstone direction follows first principles.

Findings

  1. P2: connection-owned credential mutations still accept a retired row. credential.vault.set with a request_headers locator and connection.request-headers.replace both remain remotely granted and both commit against a retained claude-subscription connection. I reproduced both on this exact head. The catalog update guard therefore does not establish the stated read/delete-only invariant.

  2. P2: the Desktop detail still exposes mutating controls that conflict with the new storage contract. Request-body saves and enabled-model changes call connection.catalog.update, which now deterministically throws invalid_connection_input for the retired row. The request-header editor is worse: it remains writable through the credential path above. A user sees a retirement alert beside controls that either fail or mutate a connection described as inert.

  3. P2: existing Sessions bound to this provider are not projected as stale in the task rail. deriveStaleSessionIds() recognizes only connection_missing and fake_backend, so provider_retired tasks look healthy until opened even though they can never send. The new active-task health copy does not cover that sidebar projection.

First-principles and Occam assessment

The simplest coherent final structure is one tombstone invariant: retired rows may be decoded, queried, exported if required for visibility, and deleted; every connection-owned write is refused centrally. The renderer should consume that same capability and render a read-only retirement detail with deletion as the only action. provider_retired should join the existing stale-session projection. Continuing to add isolated guards while leaving sibling mutation and presentation paths open is not yet the optimal architecture.

Deletion and tests

No merge-blocking low-quality tests need deletion. Add behavioral coverage for retired request-header/credential writes, a rendered read-only detail, and provider_retired stale-session classification. The producerless request_authorization_code path is real removable code, but it is unreachable and already tracked separately in #3219, so it does not block this PR.

Merge readiness

Not ready to merge. Clean build:test, workspace typecheck, 138 focused tests, Astryx inventory, and live GitHub CI/audit all pass. Residual verification gaps are live provider login and local Windows/Linux packaging; neither changes the three deterministic findings above. git diff --check also reports one extra blank line at EOF in onboarding-hero-copy.ts.

Codex-assisted review; I verified the complete diff, current main integration, exact live head, production call chains, focused tests, and the two credential-mutation reproducers before publication.

Comment thread packages/storage/src/runtime-policy/connection-catalog-document.ts
Comment thread apps/desktop/src/renderer/settings/provider-connection-detail.tsx Outdated
Comment thread apps/desktop/src/renderer/locales/conversation-copy.ts

@hqhq1025 hqhq1025 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.

Current-main update after the review above:

main advanced to 92da51d995fa80ab3629769b2af782c2cb3e8afb at 2026-08-21 06:08:59 UTC while this review was being submitted. GitHub now reports the PR as conflicting. A fresh git merge-tree shows content conflicts in packages/runtime-host/src/protocol/index.ts and docs/astryx-surface-file-inventory.md.

The protocol conflict is substantive: current main independently uses compatibility epoch 30 for managed Runtime Host credential pairing, while this branch also uses epoch 30 for the Claude OAuth wire removal. The required rebase must preserve both wire changes and allocate the next distinct epoch, 31, then regenerate the Astryx inventory and receive a complete new-head review.

This is an additional merge blocker; it does not replace the three P2 findings in the request-changes review.

Review reproduced two writes that still committed against a retained
retired row: `credential.vault.set` with a `request_headers` locator, and
`connection.request-headers.replace`. Guarding the catalog update alone
had not established the invariant it claimed, because those are sibling
writes to the same connection — and guarding entry points one at a time
is what left them open.

Every connection-owned write now passes one assertion. The credential
vault and the request-header replacement share it with the catalog
update, so a path added later inherits the refusal instead of needing to
remember it. Reading, querying and deleting stay the exceptions, and the
regression proves the row survives its refusals: after all three writes
are rejected it is still readable and still deletable, which is the only
reason it is retained.

That refusal point moved earlier than an existing expectation: a
client-supplied OAuth token for the retired provider used to be rejected
for its credential kind and is now rejected for its connection, so that
test asserts the connection-level code with the reason stated.

The Desktop detail no longer offers what the storage layer refuses. The
advanced-request, model-management and capability sections are hidden for
a retired connection, leaving the retirement notice and deletion — the
request-header editor was the worst of them, since before the vault
refused it a user could save a header that could never reach a request.

Sessions bound to a retired provider now project as stale in the task
rail. `provider_retired` joins `connection_missing` and `fake_backend`
there: the connection still exists and is still enabled, so nothing else
about the row looks wrong and the task read as healthy until opened.

Also in this push: current main is merged, and since main independently
took epoch 30 for access-credential pairing, this wire removal takes 31.
The Astryx inventory is regenerated and `git diff --check` is clean.

Reported by @hqhq1025; the detail-page half was also observed by
@M4n5ter.

Generated-by: Claude Code
Claude-Session: https://claude.ai/code/session_014ajaRxC4jydavY9nYUFj5J
@Joob1n
Joob1n dismissed stale reviews from M4n5ter and likun666661 via 38814c6 August 21, 2026 06:35
@Joob1n

Joob1n commented Aug 21, 2026

Copy link
Copy Markdown
Contributor Author

@hqhq1025 — all three P2s and the rebase are in 38814c6. Reproducing the two credential writes rather than describing them is what made the first one land; thank you for that.

P2-1 — sibling writes. You were right that the catalog guard did not establish the invariant, and right about why: I had been guarding entry points one at a time, and the credential vault and the request-header replacement are siblings of the update I had just closed. There is now one assertion — assertConnectionIsWritable — that the catalog update, setCredentialWithAuthority and replaceConnectionRequestHeaders all pass through, so a path added later inherits the refusal instead of having to remember it. The regression exercises all three of your reproducers and then asserts the row is still readable and still deletable, since a refusal that strands the credential would be worse than the writes it prevents.

One consequence worth flagging rather than burying: the refusal now happens earlier than an existing test expected. A client-supplied OAuth token for the retired provider used to be rejected for its credential kind (invalid_credential_input); it is now rejected for its connection (invalid_connection_input) before the kind is considered. I changed that assertion rather than my error code — the codes are one union, not partitioned by surface, and the connection is genuinely the subject — and left the reason in the test.

P2-2 — the detail page. Advanced request, model management and capabilities are hidden for a retired connection; what remains is the retirement notice and deletion. The request-header editor was the worst of the three, exactly as you said: before the vault refused it, a user could save a header that could never reach a request. @M4n5ter's two observations are the same surface and are covered by this.

P2-3 — the rail. provider_retired joins connection_missing and fake_backend in deriveStaleSessionIds. I also checked it stays out of shouldRebindSessionToDefault, so a retired row is never masked as recovered by another connection — same reasoning fake_backend carries there.

Epoch. Main took 30 for access-credential pairing while your review was in flight, so this takes 31, with both changelog entries preserved. Inventory regenerated (189 files); git diff --check clean, including the blank line you spotted at the end of onboarding-hero-copy.ts.

Local: build, typecheck, lint, format, Astryx inventory clean; @maka/storage 846/846 including the new cases; the runtime suite's failing set is byte-identical to the same suite at the merge base on this machine. Reverting the shared assertion turns two tests red — the new one and the OAuth-kind test above.

@hqhq1025 hqhq1025 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.

Requesting changes on head 38814c64854ff2a74aabfdc8b40eaed3528663b7.

Problem reconstruction

This PR correctly removes the unsupported Claude subscription OAuth execution path while retaining legacy rows only so users can understand and delete them. The latest delta fixes the previously reported credential/request-header writes, makes the Desktop detail read-only, marks bound tasks stale, integrates current main, and assigns compatibility epoch 31. The problem definition and retained-tombstone direction are correct and follow first principles.

Findings

  1. P2: a normal network-proxy mutation still rewrites retired rows. mutatePolicy() calls clearAllConnectionLastTests() whenever the effective proxy changes. That helper removes lastTest and increments the revision of every tested connection, including a retained retired one. I reproduced a proxy change advancing the retired row revision and clearing its test fact. This contradicts the new read/delete-only invariant and can make a concurrent deletion fail as stale even though the user edited only global proxy settings.

  2. P3: compareAndSetOAuthCredential() still bypasses the tombstone guard. With an existing retained OAuth credential, the internal store operation commits a refreshed secret because it validates only authKind. I reproduced a credential revision advancing from 1 to 2. The current production caller is gated by resolveExecutionConnection(), so this is not presently a remote execution bypass, but it leaves the claimed storage invariant incomplete.

First-principles and Occam assessment

The smallest coherent final structure is to make every connection mutation consume one retirement capability, including indirect catalog invalidations and OAuth compare-and-set. Global proxy changes should clear verification only for writable/live rows; retired tombstones should remain byte-stable until deletion. Adding more renderer or caller-specific guards is unnecessary.

Deletion and tests

No merge-blocking low-quality test should be deleted. Add one proxy-mutation regression that proves a retired row's revision and lastTest remain unchanged, plus one compare-and-set regression. The producerless authorization-code path remains removable code but is already tracked separately in #3219 and does not block this PR.

Merge readiness

Not ready to merge. Focused retirement/runtime tests passed (24 tests), Storage/Runtime/Runtime Host typechecks passed, formatting and Astryx inventory passed, and git diff --check is clean. The full build reached and passed the affected Core, Storage, Runtime, and Runtime Host packages, then stopped in unchanged packages/ui type mismatches; Desktop typecheck hit the same existing UI surface. GitHub currently reports no checks for this new head, so hosted CI is also still outstanding.

Codex-assisted adversarial re-review; I verified the exact live head, current-main merge, prior-finding fixes, production mutation paths, and both reproducers before publication.

Comment thread packages/storage/src/runtime-policy/coordinator.ts
Two indirect writes still reached the tombstone, both reproduced in
review.

A network-proxy change invalidates every tested connection's
verification, and that swept the retired row too — clearing a `lastTest`
for a provider that can no longer be tested, and bumping a revision that
a deletion started elsewhere then failed against as stale. A global
setting has no business mutating this row, so the invalidation now skips
it. One predicate decides both the early return and the rewrite, because
disagreeing there would write an identical document with a higher
revision, which is the same defect wearing a different shape. The
regression pins both directions: the live row loses its verification and
gains a revision, the retired one keeps both.

`compareAndSetOAuthCredential` validated only the auth kind, which a
retired provider still declares, so a refresh could rotate the very
credential the user is being asked to delete. It now passes the same
assertion as the other three connection writes. No production caller
reaches it — execution resolution refuses first — which is exactly why
it would have stayed open. Its regression seeds the credential the way
the row itself is seeded, since the guard now refuses to write one.

Reported by @hqhq1025, both with reproducers.

Generated-by: Claude Code
Claude-Session: https://claude.ai/code/session_014ajaRxC4jydavY9nYUFj5J
@Joob1n

Joob1n commented Aug 21, 2026

Copy link
Copy Markdown
Contributor Author

@hqhq1025 — both are in 723c149, and your framing was the useful part: I had been treating "refuse writes" as the invariant, when the invariant is that a tombstone stays byte-stable until deletion. Direct refusals do not get you there, because the two paths you found are not writes to the connection at all — one is a global operation that happens to sweep it, and one is a refresh that never asked whether the row was still alive.

P2 — global invalidation. The proxy change now leaves retired rows alone. One predicate decides both the early return and the rewrite: had I guarded only the map, the early return would still have seen work to do and written an identical document with a higher revision — the same defect in a different shape, and the kind that survives a shallow test. The regression pins both directions, because a version that invalidates nothing at all would otherwise pass it: the live row loses its lastTest and gains a revision, the retired row keeps both. Your stale-delete consequence is what makes this more than tidiness, and it is the reason the assertion is on the revision and not just the lastTest.

P3 — compare-and-set. Now behind the same assertion as the other three connection writes. Worth stating plainly: it is unreachable in production today, and that is precisely why it was still open — nothing exercised it, so nothing failed. Its regression has to seed the credential by writing the vault document directly, because the guard added earlier refuses to create one; that is the same shape as seeding the connection row, and it reproduces the real case, which is a credential written before the provider was retired. After the refusal the credential is still at revision 1 — the point being that the thing the user came to delete has not been quietly rewritten.

That makes four direct write paths through one assertion (catalog update, credential vault, request headers, OAuth refresh) plus one global operation that now excludes tombstones rather than refusing — the distinction being that a proxy edit is legitimate and simply has no business touching this row.

Local: build, typecheck, lint, format, Astryx inventory clean; git diff --check clean; @maka/storage 51/51 in the runtime-policy suite with both new cases; the runtime suite's failing set is byte-identical to the same suite at the merge base on this machine. Reverting either guard turns exactly its own regression red. Hosted CI on this head still needs the workflow approval.

@Joob1n

Joob1n commented Aug 21, 2026

Copy link
Copy Markdown
Contributor Author

@hqhq1025 — when you have a moment, could you take another look at 723c149? Both reproducers you filed are closed, and with them every finding from your three reviews on this PR: the epoch, retained-credential Session admission, retired default targets, the config-restore abort, the catalog update, the two credential writes, the read-only detail, the stale-task projection, and now the proxy invalidation and the OAuth refresh. If you would rather wait for hosted CI on this head, that is fair — it needs a committer to start the runs, and I have asked separately.

@likun666661
likun666661 dismissed hqhq1025’s stale review August 21, 2026 07:31

Requested changes were addressed in subsequent commits; dismissing this stale blocking review on the newer head.

@likun666661
likun666661 merged commit 5c07585 into apache:main Aug 21, 2026
2 checks passed
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.

5 participants