Skip to content

refactor: replace Bun.serve with Node http.createServer in OAuth handlers - #18327

Merged
thdxr merged 3 commits into
devfrom
refactor/oauth-node-http
Apr 6, 2026
Merged

refactor: replace Bun.serve with Node http.createServer in OAuth handlers#18327
thdxr merged 3 commits into
devfrom
refactor/oauth-node-http

Conversation

@thdxr

Copy link
Copy Markdown
Member

Summary

  • Replace Bun.serve with http.createServer in MCP OAuth callback server
  • Replace Bun.serve with http.createServer in Codex plugin OAuth server
  • Both use the same pattern: express-style request handlers with res.writeHead/res.end

…lers
Use Node's createServer for MCP OAuth callback and Codex plugin OAuth
servers instead of Bun.serve, making them work under Node.js.
thdxr added a commit that referenced this pull request Mar 20, 2026
@greptile-apps

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR refactors both OAuth servers — in mcp/oauth-callback.ts and plugin/codex.ts — to replace Bun.serve with Node.js http.createServer, aligning them with a more portable, runtime-agnostic pattern. The core request-handling logic is preserved faithfully; writeHead/end replace new Response(...) and server lifecycle is wrapped in Promises.

Key observations:

  • Note that AGENTS.md explicitly states "Use Bun APIs when possible". Replacing Bun.serve with Node.js HTTP goes against this guideline. If this is a deliberate step toward making opencode runtime-agnostic (e.g. for Node.js compatibility), that rationale should be documented.
  • packages/opencode/src/mcp/oauth-callback.ts: The migration is clean. stop() correctly awaits server.close() before clearing the reference, ensuring the port is fully released.
  • packages/opencode/src/plugin/codex.ts: stopOAuthServer sets oauthServer = undefined synchronously while server.close() is asynchronous. This creates a race condition: a subsequent startOAuthServer() call may attempt to listen() on a port that is still bound, resulting in an EADDRINUSE error. This is an inconsistency with how oauth-callback.ts handles the same teardown.

Confidence Score: 3/5

  • Mostly safe, but the async teardown race in codex.ts stopOAuthServer could cause EADDRINUSE on rapid re-authentication
  • The oauth-callback.ts migration is correct and the logic is functionally equivalent to the Bun.serve version. However, codex.ts has a real teardown race condition introduced by this refactor that wasn't present with Bun.serve's synchronous stop(), and the change contradicts the AGENTS.md "Use Bun APIs when possible" guideline without explanation.
  • packages/opencode/src/plugin/codex.ts — specifically the stopOAuthServer function at line 324

Important Files Changed

FilenameOverview
packages/opencode/src/mcp/oauth-callback.tsBun.serve replaced with http.createServer; logic is functionally equivalent, stop() correctly awaits close, minor style issue with inline import types in handleRequest signature
packages/opencode/src/plugin/codex.tsBun.serve replaced with http.createServer; stopOAuthServer sets oauthServer to undefined synchronously while close() is async, creating a race condition if startOAuthServer is called before the port is fully released

Sequence Diagram

sequenceDiagram
participant App
participant Server as http.createServer
participant Handler as Request Handler
participant Pending as pendingOAuth/pendingAuths
App->>Server: createServer(handler) + listen(PORT)
Note over Server: Listening on PORT
App->>Pending: waitForOAuthCallback(state)
Note over App: Browser redirected to localhost
Server->>Handler: GET /auth/callback?code=X&state=Y
Handler->>Pending: resolve(code or tokens)
Handler->>Server: res.writeHead(200) + res.end(HTML_SUCCESS)
alt codex.ts stopOAuthServer
App->>Server: server.close(cb) — async
App->>App: oauthServer = undefined — SYNC (race!)
Note over Server: Port may still be bound when next startOAuthServer runs
else oauth-callback.ts stop
App->>Server: await server.close(cb)
Note over Server: Port released before returning
App->>App: server = undefined
end
Loading

Last reviewed commit: "refactor: replace Bu..."

Comment on lines 324 to 331
function stopOAuthServer() {
if (oauthServer) {
oauthServer.stop()
oauthServer.close(() => {
log.info("codex oauth server stopped")
})
oauthServer = undefined
log.info("codex oauth server stopped")
}
}

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.

P1Race condition: port still in use after stopOAuthServer returns

oauthServer is set to undefined synchronously, but server.close() in Node.js is asynchronous — the underlying port is only released once the close callback fires. If startOAuthServer() is called again before that callback fires (e.g. the user immediately re-initiates auth), oauthServer is undefined so the early-return guard is bypassed, and the new listen() call will fail with EADDRINUSE.

By contrast, Bun.serve.stop() released the port synchronously. The sibling McpOAuthCallback.stop() correctly awaits the close before clearing the reference:

awaitnewPromise<void>((resolve)=>server!.close(()=>resolve()))server=undefined

stopOAuthServer should follow the same pattern (make it async and await the close) or at minimum only clear oauthServer inside the close callback:

functionstopOAuthServer(){if(oauthServer){constclosing=oauthServeroauthServer=undefinedclosing.close(()=>{log.info("codex oauth server stopped")})}}

Note: even the snippet above doesn't fully close the port before returning. If true synchronous-style safety is needed, the function should be made async and awaited at the call site.


export async function ensureRunning(): Promise<void> {
if (server) return
function handleRequest(req: import("http").IncomingMessage, res: import("http").ServerResponse) {

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.

P2Inline dynamic import types are unnecessary

import("http").IncomingMessage and import("http").ServerResponse are used as inline type expressions, but http is already imported at line 2. The types can be imported alongside createServer to keep the signature readable.

Suggested change
functionhandleRequest(req: import("http").IncomingMessage,res: import("http").ServerResponse){
functionhandleRequest(req: import("http").IncomingMessage,res: import("http").ServerResponse){

Change the top-level import to:

import{createServer,typeIncomingMessage,typeServerResponse}from"http"

Then the function signature becomes:

functionhandleRequest(req: IncomingMessage,res: ServerResponse){

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@thdxrthdxr added the beta label Mar 20, 2026
opencode-agentBot added a commit that referenced this pull request Mar 20, 2026
opencode-agentBot added a commit that referenced this pull request Mar 20, 2026
opencode-agentBot added a commit that referenced this pull request Mar 20, 2026
opencode-agentBot added a commit that referenced this pull request Mar 20, 2026
opencode-agentBot added a commit that referenced this pull request Mar 20, 2026
opencode-agentBot added a commit that referenced this pull request Mar 20, 2026
@thdxrthdxr added beta and removed beta labels Mar 20, 2026
opencode-agentBot added a commit that referenced this pull request Mar 20, 2026
opencode-agentBot added a commit that referenced this pull request Mar 20, 2026
opencode-agentBot added a commit that referenced this pull request Mar 20, 2026
opencode-agentBot added a commit that referenced this pull request Mar 20, 2026
opencode-agentBot added a commit that referenced this pull request Mar 20, 2026
@thdxrthdxr added beta and removed beta labels Mar 20, 2026
opencode-agentBot added a commit that referenced this pull request Mar 20, 2026
opencode-agentBot added a commit that referenced this pull request Mar 20, 2026
opencode-agentBot added a commit that referenced this pull request Mar 20, 2026
opencode-agentBot added a commit that referenced this pull request Mar 20, 2026
opencode-agentBot added a commit that referenced this pull request Mar 20, 2026
opencode-agentBot added a commit that referenced this pull request Mar 20, 2026
opencode-agentBot added a commit that referenced this pull request Mar 21, 2026
opencode-agentBot added a commit that referenced this pull request Mar 21, 2026
opencode-agentBot added a commit that referenced this pull request Mar 21, 2026
opencode-agentBot added a commit that referenced this pull request Mar 21, 2026
opencode-agentBot added a commit that referenced this pull request Mar 21, 2026
opencode-agentBot added a commit that referenced this pull request Mar 22, 2026
opencode-agentBot added a commit that referenced this pull request Mar 22, 2026
opencode-agentBot added a commit that referenced this pull request Mar 22, 2026
opencode-agentBot added a commit that referenced this pull request Mar 22, 2026
opencode-agentBot added a commit that referenced this pull request Mar 22, 2026
opencode-agentBot added a commit that referenced this pull request Mar 22, 2026
opencode-agentBot added a commit that referenced this pull request Mar 22, 2026
opencode-agentBot added a commit that referenced this pull request Mar 22, 2026
opencode-agentBot added a commit that referenced this pull request Mar 22, 2026
opencode-agentBot added a commit that referenced this pull request Mar 22, 2026
opencode-agentBot added a commit that referenced this pull request Mar 22, 2026
opencode-agentBot added a commit that referenced this pull request Mar 22, 2026
opencode-agentBot added a commit that referenced this pull request Mar 22, 2026
opencode-agentBot added a commit that referenced this pull request Mar 22, 2026
opencode-agentBot added a commit that referenced this pull request Mar 22, 2026
opencode-agentBot added a commit that referenced this pull request Mar 22, 2026
opencode-agentBot added a commit that referenced this pull request Mar 22, 2026
opencode-agentBot added a commit that referenced this pull request Mar 22, 2026
@opencode-agent

Copy link
Copy Markdown
Contributor

⚠️Blocking Beta Release

This PR cannot be merged into the beta branch due to: Merge failed

Please resolve this issue to include this PR in the next beta release.

@sjqwert

Copy link
Copy Markdown

⚠️Blocking Beta Release

This PR cannot be merged into the beta branch due to: Merge failed

Please resolve this issue to include this PR in the next beta release.

1 similar comment
@sjqwert

Copy link
Copy Markdown

⚠️Blocking Beta Release

This PR cannot be merged into the beta branch due to: Merge failed

Please resolve this issue to include this PR in the next beta release.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@thdxr@sjqwert@Hona