🤖 refactor: convert coderOauthService internals to Effect and adopt handlerGen for coder OAuth procedures - #4035
Merged
Merged
Conversation
…dlerGen for coder OAuth procedures
This comment has been minimized.
This comment has been minimized.
Member
Author
|
@codex review |
Member
Author
|
@codex security review |
|
Codex Review: Didn't find any major issues. Keep it up! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
This comment has been minimized.
This comment has been minimized.
github-merge-queue
Bot
removed this pull request from the merge queue due to failed status checks
Sep 1, 2026
This was referenced Sep 1, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Phase 6b of the progressive Effect migration (Wave 2): converts
coderOauthServiceinternals to Effect-native pipelines and moves all 5coderOauthrouter procedures ontohandlerGen, following the house pattern established in #4033/#4034 (tagged reason-carrying errors,Effect.geninternals, uninterruptible flow-starting mutations and cancel/teardown surfaces, forked callback pipelines, thinEffect.runPromisefacades, sharedtoWireResultat facades). Existing tests pass unchanged.Background
Phase 6b was deliberately split out of #4034 by the Phase 5/6a reports:
coderOauthService.ts(~2,200 lines) carries correctness-critical finish/persist sequencing — a cancelled flow must never commit a replacement login — that must not be buried under mechanical churn. This PR converts the service while preserving that contract structurally rather than re-deriving it.Implementation
CoderOauthError(Schema.TaggedError, reason-carrying) + sharedtoWireResultfold at every facade;CoderTokenRequestResultstays a success-channel union because callers branch oninvalidGrant.startDesktopFlowEffect(uninterruptible),waitForDesktopFlowEffect(interruptible delegate),cancelDesktopFlowEffect(uninterruptible),disconnectEffect(uninterruptible),refreshModelsEffect,getValidAuthEffect. Promise facades preserved 1:1 for pre-Effect callers.withCoderOauthLoginCommitLock/withCoderOauthRefreshLock) stay callback-owned Promise seams.commitDesktopLoginCrossProcessandrollbackPersistedAuthare byte-identical: thedesktopFlows.has(flowId)liveness checks remain textually adjacent to the persist write (inside the locked write predicate) and to thefinishcalls, with no new awaits inserted between check and commit. The Effect layer wraps around the critical section (commitDesktopLoginLockedEffectowns the process-local mutex viaacquireUseReleaseand folds lock rejections into the flow-finishing "failed" outcome), never through it.getValidAuth:Effect.acquireUseReleasemutex pattern from 🤖 refactor: convert codex/governor/copilot OAuth services to Effect and adopt handlerGen for OAuth procedures #4034'scodexOauthService; the refresh pipeline (refreshTokensEffect) ends in a whole-pipelineEffect.catchDefectfold, andgetValidAuthEffectfolds cross-process lock-acquisition defects, so the facade never rejects (previously a lock failure rejected).Effect.runForkfibers mirroringdesktopCallbackPipelineinmuxGatewayOauthService/codexOauthService.coderOauthprocedures (startDesktopFlow,waitForDesktopFlow,cancelDesktopFlow,disconnect,refreshModels) now ridehandlerGen, completing OAuth router coverage started in 🤖 refactor: convert codex/governor/copilot OAuth services to Effect and adopt handlerGen for OAuth procedures #4034.Pre-review audits (the #4034 lessons, applied up front)
finishEffect(already uninterruptible from 🤖 refactor: convert OAuthFlowManager flow lifecycle to Effect per-flow Scope #4033).waitForDesktopFlowEffectstays interruptible by design (template).response.json()is read via a caughttryPromisethunk typedPromise<unknown>and validated withisPlainObject/Array.isArraybefore any dereference.requestTokensEffect,revokeTokensEffect,quarantineStoredClientEffect,fetchGatewayProvidersEffect,fetchProviderCatalogEffect,validateDeploymentEffect,discoverEndpointsEffect,registerClientEffect,updateClientRedirectUriEffect— the last preserving theonUncertainOutcomeside effect in its catch), andgetValidAuth/refreshModelsgained folds so those facades never reject.Deliberate behavior deltas (rejection → wire
Err)Pre-Effect,
getValidAuth()/refreshModels()rejected when cross-process lock acquisition or a config write threw; they now returnErr(...)with the failure message. No test pinned the rejection behavior; all other wire behavior (URLs, request bodies, headers, error strings, persistence predicates) is unchanged.Validation
src/node/services/coderOauthService.test.ts: 88/88 pass unchanged — including the tests that pin the cancelled-flow no-commit contract ("does not persist tokens when the flow is cancelled during the exchange", "rolls back persisted credentials when cancelled during the persist write", "keeps the persisted login unrevoked when a post-persist cancel's rollback write fails", "disconnect cancels an in-flight re-login so it cannot commit afterwards", both overlapping-cancel snapshot tests). No new test added: the invariant was already pinned from multiple angles, and a new one would have been redundant.oauthFlowManager,oauthUtils,effectBridge,codexOauthService,muxGatewayOauthService,muxGovernorOauthService,copilotOauthService,mcpOauthService,providerModelFactory,coderService(426 tests).make static-checkgreen.Risks
Medium-touch conversion of a correctness-critical file. Highest-risk areas: (1) commit-path sequencing — mitigated by keeping the locked critical sections byte-identical Promise seams and relying on the extensive race-pinning test suite; (2) interruption semantics on handlerGen — mitigated by making every mutation/teardown surface uninterruptible and leaving only the wait surface interruptible; (3)
Effect.runForkreplacingvoid (async ...)for the callback/lease pipelines — same detached semantics, validated by the cancel/timeout/lease tests.Lessons for Phase 7 (Config service: 19 router sites, file locks → Semaphore)
withProvidersFileLock-style) convert cleanly by keeping the callback interior as a Promise seam and bridging withEffect.runPromise(toWireResult(...))at the boundary; check-adjacent-to-write invariants survive verbatim. Replace a lock with an EffectSemaphoreonly when the acquire/release sides can both move into Effect in the same change — a half-converted lock is worse than a wrapped one.AsyncMutex→Effect.acquireUseReleasetransfers mechanically (acquire viaEffect.promise, release vialock[Symbol.asyncDispose]()), oneacquireUseReleaseper resource, never combined. For Phase 7's config file locks this maps 1:1 onto aSemaphore.withPermits(1)shape later.refreshModels.Generated with
xum• Model:anthropic:claude-fable-5• Thinking:xhigh