Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

Decouple the config facade: a 222-module dependency cycle undermines the core-path guarantee

Closed
#6,386 3 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
5/5
Estimated time
Over a week
Newbie friendliness
25/100
Issue type
Refactor
Clarity
Mostly clear
Activity status
Active
Tech stack
typescript
Domain
backend, tooling

Research direction

Start with the runtime import graph and the import matcher used by tests/lab/core-lab-boundary.test.ts; the issue identifies several smaller cycles to inspect first. Then trace callers through src/config.ts and the owning src/config/ submodules. Done means the 222-module component is broken and src/config.ts is a thin compatibility re-export.

Written by the indexing model from the issue text.

Description

account-pool enhancement
Area

Multiple areas

What are you trying to accomplish?

Keep the proxy module layering intact so that a user who configures one provider and one model executes no optional-subsystem code, and so that a change in one domain stays locally scoped instead of being able to reach almost every other module. Today a single strongly-connected component of 222 runtime modules means nearly any edit can reach nearly any other module, and the three files this repository explicitly protects from Compatibility Lab (src/router.ts, src/server/lifecycle.ts, src/server/responses/core.ts) all sit inside that component.

What prevents this today?

src/config.ts is a 386-line facade that re-exports the already-split src/config/ submodules (config/paths, config/subagent-models, config/salvage, config/diagnostics, config/load-degrade, config/provider-name, config/warn-memo and others). The implementations were physically moved out, but about 198 modules, the highest fan-in in the tree, still import everything through the single config entry rather than from the module that actually owns the concern. That is what keeps the cycle closed.

Measured on the runtime import graph (value imports only; import type is excluded because it is erased at runtime), across 1598 modules and 7655 edges:

  • 6 strongly-connected components with more than one module; 238 modules sit inside a cycle.
  • The largest component holds 222 modules: bridge, config, router, oauth/index, all 30 codex/catalog modules, providers/quota, server/lifecycle, service, storage and usage/log among them.
  • Of 228 back edges, 92 (about 40 percent) terminate at config. The next highest target is codex/main-account with 26, then oauth/index with 19.

Because the facade is the single return target, splitting config further cannot break the cycle: the split already happened and the cycle survived it.

What should OpenCodex do?

Callers that need exactly one concern should import from the src/config submodule that owns it, or from a narrow per-concern facade, so that src/config.ts shrinks to a thin compatibility re-export and the import graph becomes layered instead of cyclic. Concretely, break the 222-module component: start with the small cycles, then move per-domain callers off the config facade. The Compatibility Lab decoupling already proved the slot-registration pattern works (devlog/_fin/260814_lab_core_decoupling/); the same technique has simply never been applied to config. Note that bun run structure:check passes today, so no structure doc is stale: the layering is just not recorded as an invariant anywhere.

Example usage or interface

Before, in about 198 modules:

import { getConfigDir, loadConfig } from "../config";

After, importing the owning module directly:

import { getConfigDir } from "../config/paths";

import { loadConfig } from "../config/load-degrade";

Smaller cycles worth fixing first, each verified by hand against the source:

  • oauth/chatgpt and oauth/chatgpt-device (2 modules)
  • server/claude-messages and server/messages-native (2 modules)
  • claude/desktop-first-party to desktop-picker to intercept/picker-runtime to intercept/runtime (4 modules)
  • client/machine-listener and client/runtime (2 modules)

One real cycle inside the 222-module component, every link source-checked: usage/log to codex/account-label to codex/main-account to codex/native-profile-startup to codex/native-profile-manager to codex/native-profile-processes to codex/app-server-processes to codex/catalog/parsing to oauth/index to providers/api-keys to providers/api-key-selection to router to routing/evaluator to routing/health to routing/history/indexer and back to usage/log.

Alternatives or workarounds

Leaving src/config.ts as the single entry point and documenting the cycle is the status quo. It works, but it means the guarantee that an optional subsystem stays off the core path rests on a foundation where every protected file is cyclically coupled to every other one. Splitting config into more submodules without changing call sites was already tried and did not break the cycle.

Additional context

Related but separate: issue 6297 (native Anthropic passthrough drops anthropic-ratelimit response headers), verified still present on main. That is a bug, not this refactor.

The numbers above come from walking the runtime import graph with the same import matcher that tests/lab/core-lab-boundary.test.ts uses; type-only imports are excluded.

Checks
  • 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.
Dominant language
TypeScript
Stars
16.9k
Forks
1.3k
Avg merge
9h 39m
Merged PRs (30d)
633

Getting set up

This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from lidge-jun/opencodex

All issues in lidge-jun/opencodex

Similar issues

More TypeScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.