Skip to content

Commit fb07c20

Browse files
authored
fix(server): provide fresh ConfigProvider per HttpApi listener (#25726)
1 parent 25dc6f0 commit fb07c20

2 files changed

Lines changed: 25 additions & 17 deletions

File tree

packages/opencode/src/server/server.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { lazy } from "@/util/lazy"
55
import * as Log from "@opencode-ai/core/util/log"
66
import { Flag } from "@opencode-ai/core/flag/flag"
77
import { WorkspaceID } from "@/control-plane/schema"
8-
import { Context, Effect, Exit, Layer, Scope } from "effect"
8+
import { ConfigProvider, Context, Effect, Exit, Layer, Scope } from "effect"
99
import { HttpRouter, HttpServer } from "effect/unstable/http"
1010
import { OpenApi } from "effect/unstable/httpapi"
1111
import * as HttpApiServer from "#httpapi-server"
@@ -259,6 +259,12 @@ async function listenHttpApi(opts: ListenOptions, selection: ServerBackend.Selec
259259
}).pipe(
260260
Layer.provideMerge(WebSocketTracker.layer),
261261
Layer.provideMerge(HttpApiServer.layer({ port, hostname: opts.hostname })),
262+
// Install a fresh `ConfigProvider` per listener so `Config.string(...)`
263+
// reads reflect the current `process.env`. Effect's default
264+
// `ConfigProvider` snapshots `process.env` on first read and caches the
265+
// result on a module-singleton Reference; without overriding it here,
266+
// every later `Server.listen()` keeps observing that initial snapshot.
267+
Layer.provide(ConfigProvider.layer(ConfigProvider.fromEnv())),
262268
)
263269

264270
const start = async (port: number) => {

packages/opencode/test/server/httpapi-listen.test.ts

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ async function startListener(backend: "effect-httpapi" | "hono" = "effect-httpap
4040
return Server.listen({ hostname: "127.0.0.1", port: 0 })
4141
}
4242

43-
async function startNoAuthListener() {
44-
Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = false
43+
async function startNoAuthListener(backend: "effect-httpapi" | "hono" = "effect-httpapi") {
44+
Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = backend === "effect-httpapi"
4545
Flag.OPENCODE_SERVER_PASSWORD = undefined
4646
Flag.OPENCODE_SERVER_USERNAME = auth.username
4747
delete process.env.OPENCODE_SERVER_PASSWORD
@@ -300,18 +300,20 @@ describe("HttpApi Server.listen", () => {
300300
}
301301
})
302302

303-
testPty("keeps PTY websocket tickets optional when server auth is disabled", async () => {
304-
await using tmp = await tmpdir({ git: true, config: { formatter: false, lsp: false } })
305-
const listener = await startNoAuthListener()
306-
try {
307-
const info = await createCat(listener, tmp.path)
308-
const ws = await openSocket(socketURL(listener, info.id, tmp.path))
309-
const message = waitForMessage(ws, (message) => message.includes("ping-no-auth"))
310-
ws.send("ping-no-auth\n")
311-
expect(await message).toContain("ping-no-auth")
312-
ws.close(1000)
313-
} finally {
314-
await stop(listener, "timed out cleaning up no-auth listener").catch(() => undefined)
315-
}
316-
})
303+
for (const backend of ["effect-httpapi", "hono"] as const) {
304+
testPty(`keeps PTY websocket tickets optional when server auth is disabled (${backend})`, async () => {
305+
await using tmp = await tmpdir({ git: true, config: { formatter: false, lsp: false } })
306+
const listener = await startNoAuthListener(backend)
307+
try {
308+
const info = await createCat(listener, tmp.path)
309+
const ws = await openSocket(socketURL(listener, info.id, tmp.path))
310+
const message = waitForMessage(ws, (message) => message.includes(`ping-no-auth-${backend}`))
311+
ws.send(`ping-no-auth-${backend}\n`)
312+
expect(await message).toContain(`ping-no-auth-${backend}`)
313+
ws.close(1000)
314+
} finally {
315+
await stop(listener, "timed out cleaning up no-auth listener").catch(() => undefined)
316+
}
317+
})
318+
}
317319
})

0 commit comments

Comments
 (0)