Browse Source

fix(server): provide fresh ConfigProvider per HttpApi listener (#25726)

Kit Langton 3 tháng trước cách đây
mục cha
commit
fb07c2070c

+ 7 - 1
packages/opencode/src/server/server.ts

@@ -5,7 +5,7 @@ import { lazy } from "@/util/lazy"
 import * as Log from "@opencode-ai/core/util/log"
 import { Flag } from "@opencode-ai/core/flag/flag"
 import { WorkspaceID } from "@/control-plane/schema"
-import { Context, Effect, Exit, Layer, Scope } from "effect"
+import { ConfigProvider, Context, Effect, Exit, Layer, Scope } from "effect"
 import { HttpRouter, HttpServer } from "effect/unstable/http"
 import { OpenApi } from "effect/unstable/httpapi"
 import * as HttpApiServer from "#httpapi-server"
@@ -259,6 +259,12 @@ async function listenHttpApi(opts: ListenOptions, selection: ServerBackend.Selec
     }).pipe(
       Layer.provideMerge(WebSocketTracker.layer),
       Layer.provideMerge(HttpApiServer.layer({ port, hostname: opts.hostname })),
+      // Install a fresh `ConfigProvider` per listener so `Config.string(...)`
+      // reads reflect the current `process.env`. Effect's default
+      // `ConfigProvider` snapshots `process.env` on first read and caches the
+      // result on a module-singleton Reference; without overriding it here,
+      // every later `Server.listen()` keeps observing that initial snapshot.
+      Layer.provide(ConfigProvider.layer(ConfigProvider.fromEnv())),
     )
 
   const start = async (port: number) => {

+ 18 - 16
packages/opencode/test/server/httpapi-listen.test.ts

@@ -40,8 +40,8 @@ async function startListener(backend: "effect-httpapi" | "hono" = "effect-httpap
   return Server.listen({ hostname: "127.0.0.1", port: 0 })
 }
 
-async function startNoAuthListener() {
-  Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = false
+async function startNoAuthListener(backend: "effect-httpapi" | "hono" = "effect-httpapi") {
+  Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = backend === "effect-httpapi"
   Flag.OPENCODE_SERVER_PASSWORD = undefined
   Flag.OPENCODE_SERVER_USERNAME = auth.username
   delete process.env.OPENCODE_SERVER_PASSWORD
@@ -300,18 +300,20 @@ describe("HttpApi Server.listen", () => {
     }
   })
 
-  testPty("keeps PTY websocket tickets optional when server auth is disabled", async () => {
-    await using tmp = await tmpdir({ git: true, config: { formatter: false, lsp: false } })
-    const listener = await startNoAuthListener()
-    try {
-      const info = await createCat(listener, tmp.path)
-      const ws = await openSocket(socketURL(listener, info.id, tmp.path))
-      const message = waitForMessage(ws, (message) => message.includes("ping-no-auth"))
-      ws.send("ping-no-auth\n")
-      expect(await message).toContain("ping-no-auth")
-      ws.close(1000)
-    } finally {
-      await stop(listener, "timed out cleaning up no-auth listener").catch(() => undefined)
-    }
-  })
+  for (const backend of ["effect-httpapi", "hono"] as const) {
+    testPty(`keeps PTY websocket tickets optional when server auth is disabled (${backend})`, async () => {
+      await using tmp = await tmpdir({ git: true, config: { formatter: false, lsp: false } })
+      const listener = await startNoAuthListener(backend)
+      try {
+        const info = await createCat(listener, tmp.path)
+        const ws = await openSocket(socketURL(listener, info.id, tmp.path))
+        const message = waitForMessage(ws, (message) => message.includes(`ping-no-auth-${backend}`))
+        ws.send(`ping-no-auth-${backend}\n`)
+        expect(await message).toContain(`ping-no-auth-${backend}`)
+        ws.close(1000)
+      } finally {
+        await stop(listener, "timed out cleaning up no-auth listener").catch(() => undefined)
+      }
+    })
+  }
 })