Skip to content

refactor(request): split fetch-helpers along its natural seams - #524

Merged
ndycode merged 2 commits into
mainfrom
claude/audit-06-fetch-helpers-split
Jun 10, 2026
Merged

refactor(request): split fetch-helpers along its natural seams#524
ndycode merged 2 commits into
mainfrom
claude/audit-06-fetch-helpers-split

Conversation

@ndycode

@ndycode ndycode commented Jun 10, 2026

Copy link
Copy Markdown
Owner

Summary

Splits lib/request/fetch-helpers.ts (1,497 lines, 30+ exports) along its natural seams into four focused modules, per audit roadmap §4.1.2 (docs/audits/AUDIT_2026-06-10.md, PR #522). All code moved verbatim; fetch-helpers.ts remains the re-export facade, so no importer changes and zero behavior change.

Changes

File Lines Contents
lib/request/token-refresh.ts 147 shouldRefreshToken, refreshAndUpdateToken + private refresh-retry predicates
lib/request/error-classification.ts 366 isEntitlementError, isWorkspaceDisabledError, EntitlementError, full unsupported-model fallback machinery
lib/request/url-rewriting.ts 199 extractRequestUrl, rewriteUrlForCodex, proxy resolution, shared dispatcher cache + cleanup
lib/request/headers.ts 99 createCodexHeaders (all overloads), RFC 8594 deprecation/sunset warning logging
lib/request/fetch-helpers.ts 1,497 → 756 Response/error orchestration and rate-limit parsing stay (they span concerns); explicit re-exports of every previously-public symbol
  • Re-exports are explicit lists (not export *), so the facade's public surface did not grow.
  • Three previously-private helpers gained export in their new homes only, for cross-module use: CHATGPT_CODEX_UNSUPPORTED_MODEL_CODE, isUnsupportedCodexModelForChatGpt, logDeprecationHeaders.
  • Runtime export surface compared before/after via namespace-import dump: byte-identical. No import cycles among the five request modules (madge).

Validation

  • npm run typecheck
  • npx eslint lib/request/ --max-warnings=0
  • npx vitest run on all suites referencing fetch-helpers (fetch-helpers, quota-probe, index, index-retry, public-api-contract, chaos/fault-injection): 358/358 passed
  • Independently re-verified: typecheck + fetch-helpers/public-api-contract suites, 151/151

Risk / Rollback

Mechanical move with a facade; revert the single commit to roll back. No data, config, or behavior changes.

https://claude.ai/code/session_01XNtnkLbBiXZxfQQYLMpucB


Generated by Claude Code

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

Greptile Summary

splits lib/request/fetch-helpers.ts (1,497 lines) along natural seams into four focused modules (token-refresh, error-classification, url-rewriting, headers), keeping fetch-helpers.ts as an explicit re-export facade with zero behavior change. all code is moved verbatim; existing tests (358/358) pass unchanged.

  • four new files extract token refresh, error classification, url/proxy logic, and header construction; each is standalone with no cross-module imports among themselves.
  • facade re-export lists are explicit (not export *), preserving the exact public surface; three helpers gain @internal exports in sub-modules for use by fetch-helpers.ts itself, but are not forwarded through the facade.
  • registerCleanup(closeSharedProxyDispatchers) is moved to url-rewriting.ts module-load time; behavior is unchanged since es module singletons guarantee it fires exactly once.

Confidence Score: 5/5

mechanical extraction with a re-export facade; all code is verbatim, public surface is byte-identical, and 358 tests pass — safe to merge.

every function, constant, and interface moved verbatim; no logic changes, no import cycles, no new behavior. the only new surface is three @internal-annotated helpers accessible via sub-path imports, which the previous thread already covers. the change is structurally clean.

no files need extra scrutiny; all five files carry straightforward extractions or re-export plumbing.

Important Files Changed

Filename Overview
lib/request/error-classification.ts new file; verbatim extraction of entitlement/unsupported-model logic from fetch-helpers.ts; three @internal exports (CHATGPT_CODEX_UNSUPPORTED_MODEL_CODE, isUnsupportedCodexModelForChatGpt) not re-exposed through facade — flagged in previous thread
lib/request/fetch-helpers.ts trimmed to facade + response/rate-limit orchestration; explicit re-export lists correctly preserve every previously-public symbol
lib/request/headers.ts new file; verbatim extraction of createCodexHeaders overloads + logDeprecationHeaders; logDeprecationHeaders exported @internal, not re-exposed through facade — flagged in previous thread
lib/request/token-refresh.ts new file; verbatim extraction of shouldRefreshToken, refreshAndUpdateToken, and private retry predicates; EPERM/EBUSY retryability intact for windows token-file writes
lib/request/url-rewriting.ts new file; verbatim extraction of proxy/URL rewrite logic; registerCleanup(closeSharedProxyDispatchers) side-effect preserved at module-load time; pre-existing double-create race in getSharedProxyDispatcher flagged in previous outside-diff comment

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    FH["fetch-helpers.ts<br/>(facade + response orchestration)"]
    EC["error-classification.ts<br/>entitlement · model fallback · workspace-disabled"]
    H["headers.ts<br/>createCodexHeaders · logDeprecationHeaders"]
    TR["token-refresh.ts<br/>shouldRefreshToken · refreshAndUpdateToken"]
    UR["url-rewriting.ts<br/>proxy resolution · URL rewrite<br/>+ registerCleanup side-effect"]

    EC -->|"re-exported via facade"| FH
    TR -->|"re-exported via facade"| FH
    UR -->|"re-exported via facade"| FH
    H  -->|"re-exported via facade"| FH

    FH -->|"internal import"| EC
    FH -->|"internal import"| H

    UR -->|"registerCleanup at load"| SD["shutdown.js"]
    TR -->|"queuedRefresh"| RQ["refresh-queue.js"]
Loading

Comments Outside Diff (1)

  1. lib/request/url-rewriting.ts, line 1671-1680 (link)

    P2 concurrency: unguarded double-create in getSharedProxyDispatcher

    two concurrent callers can both observe sharedProxyDispatchers.get(proxyUrl) === undefined, both construct a new ProxyAgent, and the first agent gets silently overwritten in the map — leaving an unclosed ProxyAgent that is never passed to closeSharedProxyDispatchers. this is pre-existing code moved verbatim, but isolating it in its own module makes it the single owner of sharedProxyDispatchers; no other module can close the leaked agent. on windows, an abandoned ProxyAgent can hold an open socket handle that blocks cleanup. consider checking-after-set or using a pending promise to serialize construction.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: lib/request/url-rewriting.ts
    Line: 1671-1680
    
    Comment:
    **concurrency: unguarded double-create in `getSharedProxyDispatcher`**
    
    two concurrent callers can both observe `sharedProxyDispatchers.get(proxyUrl) === undefined`, both construct a new `ProxyAgent`, and the first agent gets silently overwritten in the map — leaving an unclosed `ProxyAgent` that is never passed to `closeSharedProxyDispatchers`. this is pre-existing code moved verbatim, but isolating it in its own module makes it the single owner of `sharedProxyDispatchers`; no other module can close the leaked agent. on windows, an abandoned `ProxyAgent` can hold an open socket handle that blocks cleanup. consider checking-after-set or using a pending promise to serialize construction.
    
    How can I resolve this? If you propose a fix, please make it concise.

    Fix in Codex

Fix All in Codex

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

---

### Issue 1 of 1
lib/request/token-refresh.ts:1-10
**no dedicated vitest test file for the four new modules**

`token-refresh.ts`, `error-classification.ts`, `url-rewriting.ts`, and `headers.ts` have no corresponding test files. existing coverage flows entirely through the `fetch-helpers.ts` facade tests, so per-module edge cases (e.g. `isRetryableAuthSetterError` returning `true` on windows `EPERM`, the `registerCleanup` side-effect firing at module load in `url-rewriting.ts`, the `@internal`-exported helpers being callable directly from sub-paths) go untested in isolation. worth adding `test/token-refresh.test.ts` etc. to lock down the extracted seams independently.

Reviews (2): Last reviewed commit: "docs(request): mark cross-module helpers..." | Re-trigger Greptile

Splits lib/request/fetch-helpers.ts (1,497 lines) into four focused
modules, moving code verbatim:

- token-refresh.ts (147 lines): shouldRefreshToken,
  refreshAndUpdateToken, plus private CodexAuthSetter,
  isRetryableRefreshFailure, isRetryableAuthSetterError
- error-classification.ts (366 lines): isEntitlementError,
  isWorkspaceDisabledError, createEntitlementErrorResponse, the
  unsupported-Codex-model detection/fallback chain and its private
  helpers (canonicalizeModelName, normalizeFallbackChain, patterns)
- url-rewriting.ts (199 lines): extractRequestUrl, rewriteUrlForCodex,
  proxy resolution (resolveProxyUrlForRequest, applyProxyCompatibleInit,
  closeSharedProxyDispatchers) and the shared dispatcher cache
- headers.ts (99 lines): createCodexHeaders (all overloads) and the
  RFC 8594 deprecation/sunset header logging

fetch-helpers.ts (now 756 lines) remains the re-export facade for every
previously-exported symbol, so no importer changes anywhere. Response
handling (handleErrorResponse/handleSuccessResponse), rate-limit
parsing, and transformRequestForCodex stay in fetch-helpers.ts as they
do not fit a seam cleanly. Runtime export surface verified identical
before/after; no import cycles introduced. Zero behavior change.

Audit roadmap §4.1.2.

https://claude.ai/code/session_01XNtnkLbBiXZxfQQYLMpucB
@chatgpt-codex-connector

Copy link
Copy Markdown

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

@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

this pr extracts request handling utilities from fetch-helpers.ts into five focused, reusable modules: error classification (unsupported Codex models, entitlement/workspace errors, fallback chains), token refresh (OAuth refresh + persist with retryability classification), headers (Codex request headers + deprecation logging), and URL/proxy handling (URL rewriting, proxy discovery, dispatcher caching). fetch-helpers.ts is refactored to re-export these utilities while preserving core transformRequestForCodex logic.

Changes

Custom Fetch Request Handling Refactoring

Layer / File(s) Summary
Codex error classification and entitlement handling
lib/request/error-classification.ts
Stateless helpers for extracting unsupported Codex models from error payloads, parsing fallback chains with edge-case constraints (legacy gpt-5.3→gpt-5.2 gate), classifying entitlement/subscription errors by code+text+status, detecting workspace-disabled/expired states via regex+known-error-codes, and building standardized 403 entitlement error responses.
OAuth token refresh and auth persistence
lib/request/token-refresh.ts
shouldRefreshToken gates refresh necessity (OAuth type, expiry skew); refreshAndUpdateToken orchestrates queuedRefresh, validates setter, classifies transient refresh/setter failures recursively by code/status/cause, persists via authSetter.set, mutates passed auth, returns updated auth.
Request header construction and deprecation logging
lib/request/headers.ts
createCodexHeaders (named-parameter + positional overloads) validates credentials, removes x-api-key, sets Authorization/OpenAI/Codex headers, conditionally manages conversation/session IDs. logDeprecationHeaders surfaces HTTP Deprecation/Sunset headers via warning.
URL extraction, rewriting, and proxy resolution
lib/request/url-rewriting.ts
extractRequestUrl normalizes input types; rewriteUrlForCodex swaps Codex response paths + normalizes to CODEX_BASE_URL; resolveProxyUrlForRequest selects http(s)_proxy from env with no_proxy wildcard/host:port bypass matching. Process-wide undici dispatcher cache with closeSharedProxyDispatchers() cleanup; applyProxyCompatibleInit attaches resolved dispatcher unless caller provided custom agent.
Fetch helpers consolidation and re-exports
lib/request/fetch-helpers.ts
Re-exports error classification, token refresh, URL rewriting, header utilities via export { ... } from ... statements. Retains RateLimitInfo, TransformRequestForCodexResult, and rate-limit parsing constants. transformRequestForCodex preserved: optional pre-parsed body, model normalization, logging, Codex instruction fetch, body transform, deferred trim plan; logs errors and returns undefined (except "Responses background mode" re-throw). logDeprecationHeaders now imported from ./headers.js.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes


Possibly related PRs

  • ndycode/codex-multi-auth#136: integrates isWorkspaceDisabledError into 403 workspace-disabled/expired auto-rotation logic, consuming the new error classification directly.
  • ndycode/codex-multi-auth#502: fixes getUnsupportedCodexModelInfo flat {"detail": ...} error shape and Codex-unavailable probe, overlapping with the new error-classification refactor.
  • ndycode/codex-multi-auth#134: adds proxy transport helpers (applyProxyCompatibleInit, resolveProxyUrlForRequest, dispatcher cleanup) to upstream fetch/fallback requests, directly aligned with the new url-rewriting module.

Review notes

error-classification.ts

  • lib/request/error-classification.ts:130–185 (getUnsupportedCodexModelInfo): careful shape handling for errorBody — supports nested { error: { message: ... } } and flat { detail: ... } payloads. verify that both response shapes are covered in unit tests; add explicit test cases for malformed/null/non-record bodies to prevent silent failures downstream.
  • lib/request/error-classification.ts:186–221 (resolveUnsupportedCodexFallbackModel): the gpt-5.3→gpt-5.2 constraint blocks fallback unless shouldFallbackToGpt52OnUnsupportedGpt53 probes the edge active. this is a forward-compatibility gate; confirm in test that removing or modifying the constraint does not break existing callers or cache behavior during concurrent/concurrent-fallback scenarios.
  • lib/request/error-classification.ts:263–332 (isWorkspaceDisabledError): combines status-code-aware logic (402 vs 403), regex matching, and membership tests against fixed known-error-codes. regex patterns should be exhaustively tested against real OpenAI error messages; add regression test for each pattern to prevent silent error misclassification.

token-refresh.ts

  • lib/request/token-refresh.ts:62–89 (isRetryableAuthSetterError): recursively traverses cause chain to classify nested errors. risk: deep cause chains or circular references could cause stack overflow or infinite loops. add defensive depth limit and circular-reference guard.
  • lib/request/token-refresh.ts:97–147 (refreshAndUpdateToken): mutates passed currentAuth object directly when OAuth. risk: if caller holds multiple references or the refresh is interrupted between queuedRefresh return and mutation, callers may observe inconsistent state. add documentation warning and consider returning a new object or a mutation receipt instead.

url-rewriting.ts

  • lib/request/url-rewriting.ts:67–125 (no_proxy parsing): supports wildcard matching and optional host:port entries. windows edge case: env vars are case-insensitive on windows; verify that http_proxy / HTTP_PROXY / Http_Proxy all normalize correctly. add windows-specific unit test.
  • lib/request/url-rewriting.ts:152–178 (dispatcher caching): sharedProxyDispatchers is a process-wide cache keyed by proxy URL. risk: if proxy URL is re-resolved (e.g., env var changes) without cache invalidation, callers may reuse closed or stale dispatchers. add test for cache invalidation or env-change scenarios.

fetch-helpers.ts refactor

  • lib/request/fetch-helpers.ts:19–64 (re-exports): large re-export block from five new modules. verify that all re-exports are actually used by callers; unused re-exports are dead surface and increase maintenance burden. run a grep across the codebase to confirm consumption.
  • missing regression tests: this PR adds complex error-classification logic but does not include test files. add lib/request/error-classification.test.ts, lib/request/token-refresh.test.ts, lib/request/url-rewriting.test.ts, lib/request/headers.test.ts covering entitlement/unsupported-model edge cases, refresh retryability, proxy bypass matching, and header overload signatures.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.93% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed title follows conventional commits format with type(scope) and 62-char summary; accurately summarizes the refactor splitting fetch-helpers into natural seams.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed description follows template structure with summary, changes table, validation checklist, and risk/rollback sections. all required content is present.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/audit-06-fetch-helpers-split
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch claude/audit-06-fetch-helpers-split

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Comment thread lib/request/error-classification.ts
Review follow-up: the three symbols that gained export during the split
(CHATGPT_CODEX_UNSUPPORTED_MODEL_CODE, isUnsupportedCodexModelForChatGpt,
logDeprecationHeaders) exist only for sibling lib/request modules; the
facade in fetch-helpers.ts deliberately does not re-export them.

https://claude.ai/code/session_01XNtnkLbBiXZxfQQYLMpucB
@ndycode
ndycode merged commit 1dc1c0a into main Jun 10, 2026
2 checks passed
luo178 pushed a commit to luo178/codex-multi-auth that referenced this pull request Jun 23, 2026
Audit roadmap §4.3 (request layer); stacked on the fetch-helpers split
(PR ndycode#524).

Inventory result: the request layer is ALREADY fully adopted in code, so
this commit is documentation-only — no throw sites changed.

Sites already using typed contracts (verified, unchanged):
- lib/request/token-refresh.ts:103,112,133 — CodexAuthError
  (ERROR_MESSAGES.TOKEN_REFRESH_FAILED) with retryable/context/cause;
  catch sites verified: lib/refresh-guardian.ts:178 uses
  `instanceof CodexAuthError && !error.retryable`, which holds;
  test/fetch-helpers.test.ts asserts the exact message and retryable
  flags.
- lib/request/fetch-helpers.ts:212 — intentional rethrow of an error
  originating in request-transformer.ts (out of scope); left unchanged.

Sites left as-is, with reasons:
- lib/request/headers.ts:67 — `TypeError: createCodexHeaders requires
  accountId and accessToken`. The Options-Object Compatibility Contract
  documents no CodexError subclass for argument misuse, and the native
  TypeError is a deliberate convention shared with the five other
  dual-call helpers (parallel-probe, rotation, request-transformer,
  rate-limit-backoff — all outside this PR's scope). Converting only
  this site would fracture the convention and change `instanceof
  TypeError` identity. Tests assert the exact message
  (test/fetch-helpers.test.ts:614,1834,1843).
- lib/request/error-classification.ts / fetch-helpers.ts error mapping —
  the HTTP/Error Mapping Contract returns normalized Response payloads
  (stable error.message/error.code), not thrown errors; typed Error
  classes intentionally do not apply.
- lib/request/url-rewriting.ts — no throw sites (native TypeError from
  the URL constructor is platform behavior asserted by tests).

New classes: none needed; lib/errors.ts already defines every shape the
documented contracts require.

Doc change: docs/reference/error-contracts.md now references the
machine-readable types backing the contracts — a "Typed Errors"
subsection under the Fetch Helpers contract (CodexAuthError code,
message, retryable/cause/context semantics, catch-site guidance) and a
note in the Options-Object Compatibility Contract pinning the native
TypeError convention for invalid named-parameter calls.

Verification: typecheck clean; fetch-helpers, quota-probe, index,
index-retry, public-api-contract, chaos/fault-injection, documentation,
errors suites: 419/419 passed, identical to base
(origin/claude/audit-06-fetch-helpers-split) via git stash comparison.

https://claude.ai/code/session_01XNtnkLbBiXZxfQQYLMpucB
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.

2 participants