Remove the direct router and API-key-selection import cycle #3894
Description
Area
Proxy and routing
What are you trying to accomplish?
Maintain provider routing and API-key failover without coupling the router's selection-snapshot helper to the module that performs persisted selection changes and route resolution. The scope is a small dependency cleanup with unchanged routing and key-selection behavior.
What prevents this today?
At dev commit 76436a3ee221d577cc94d6c10544c03bb1f9d9db, the following direct runtime cycle exists:
src/router.ts
-> src/providers/api-key-selection.ts
-> src/router.ts
- router.ts:13 imports
captureProviderApiKeySelection. - api-key-selection.ts:6 imports
routedProviderConfigand defines the small snapshot helper.
The helper only derives entryId, reference, and revision from its provider argument. It does not need route resolution or persisted mutation. Keeping it in the larger module creates an unnecessary direct cycle.
This is a maintainability issue. No current startup crash or guaranteed module-order failure is claimed.
What should OpenCodex do?
Remove this specific direct cycle while preserving current routing, selection snapshots, and failover behavior:
- Put the snapshot helper in a small module whose dependencies are type-only, or use an equivalent existing dependency-neutral location.
- Have the router and API-key-selection module consume that helper.
- Preserve the existing helper export if it is moved, so existing imports remain compatible.
- Keep
routedProviderConfigimports in the selection and failover modules within this scope. - Add focused coverage for the helper's behavior and its runtime dependency boundary, including that the router no longer imports the stateful API-key-selection module directly.
This issue does not require making the entire router dependency graph acyclic or banning every provider-to-router dependency.
Example usage or interface
Before:
// src/router.ts
import { captureProviderApiKeySelection } from "./providers/api-key-selection";Possible after:
// src/router.ts
import { captureProviderApiKeySelection } from "./providers/api-key-selection-capture";
// src/providers/api-key-selection.ts
import { captureProviderApiKeySelection } from "./api-key-selection-capture";
export { captureProviderApiKeySelection } from "./api-key-selection-capture";
import { routedProviderConfig } from "../router";For a provider with a selected pool key and a selection revision, the captured entry ID, key reference, and revision must remain identical. Missing pool entries and optional fields must retain their existing behavior.
Alternatives or workarounds
Extracting routedProviderConfig itself would move a broader set of routing dependencies and is not necessary to remove this direct edge.
A global router cycle test would exceed this issue's scope. An in-memory graph check applying only the proposed router import change still found this other static cycle:
router -> routing/evaluator -> routing/quota -> providers/quota
-> codex/auth-api -> lib/state-store-registrations -> router
The return edge is state-store-registrations.ts:42. That broader dependency work should be evaluated separately.
Additional context
The current direct cycle was verified from source and a static runtime-import graph using Bun's TypeScript transpiler. The hypothetical extraction was evaluated in memory; no source change is included.
On this checkout, all 17 tests in tests/lab/core-lab-boundary.test.ts passed. The focused key-failover and provider-key-store test files could not load because zod/v4 was missing, so their success is not claimed. They should be run alongside the new focused coverage when implementing this change:
bun test tests/adapters/key-failover.test.ts tests/providers/provider-key-store.test.ts tests/lab/core-lab-boundary.test.tsChecks
- I searched existing issues and documentation.
- This request describes a concrete OpenCodex workflow rather than merely naming a desired technology.
- I removed secrets and personal data.
Activity
lidge-jun commentedon Sep 7, 2026
리뷰 · 우선순위 38 / 80
이 이슈는 런타임 동작 버그가 아니라 직접 순환 import 정리입니다. 지금
dev에서src/router.ts가captureProviderApiKeySelection을src/providers/api-key-selection.ts에서 가져오고, 그 파일이 다시routedProviderConfig를../router에서 가져옵니다. 스냅샷 헬퍼는entryId/reference/revision만 뽑아서 route resolve나 persist가 필요 없습니다. 큰 모듈에 두면 순환이 생깁니다.제안은 타입 위주 작은 모듈(예:
api-key-selection-capture.ts)로 헬퍼를 옮기고, router와 api-key-selection이 그걸 쓰며, 기존 export는 re-export로 호환을 유지하는 것입니다.routedProviderConfig를 뽑아내는 큰 리팩터는 범위 밖이라고 잘 그었습니다. 다른 정적 순환(router → … → state-store-registrations → router)도 따로 적혀 있어 이 이슈가 전체를 고치겠다고 과대광고하지 않습니다.src/router.ts:13 -
api-key-selection대신 capture 전용 모듈을 import하면 이 직접 간선이 끊깁니다.src/providers/api-key-selection.ts:6 -
routedProviderConfigimport는 selection/failover 본래 일에 필요하면 남겨도 됩니다. 헬퍼만 분리하면 됩니다.메인테이너의 판단이 필요한 지점
너의 추천
유지보수 이슈로 열고, 제안한 작은 추출 + 헬퍼/의존 경계 테스트 PR을 받으면 됩니다. 동작 변경이 없어야 하고, 전체를 acyclic으로 만들 필요는 없습니다.
이 댓글은 grok-bot이 작성했습니다
wp3: land four small contributor changes and the missing feedback-cal…
lidge-jun commentedon Sep 8, 2026
Fixed on
devin 164bd2b (via #4053, carrying #3897 by @parkjs101).The API-key selection capture moved into its own module,
src/providers/api-key-selection-capture.ts, andapi-key-selection.tsre-exports it, sosrc/router.tsnow reaches the helper without importing the stateful module that imported the router back. Existing importers are unaffected — the re-export resolves to the same function object, which the shipped test asserts directly.The extraction is byte-identical: same first-match order over
apiKeyPool, same captured shape, sameundefinedhandling. An independent review confirmed the cycle is gone with a runtime-import scan that distinguishes erased type imports from real dependencies, and 442 tests across fifteen downstream consumers pass.Closing manually because pull requests here target
dev, and GitHub only auto-closes on merge intomain.lidge-jun commentedon Sep 8, 2026
Landed via #4053 at 164bd2b
wp3: land four small contributor changes and the missing feedback-cal…