fix(sse): dynamic-specifier require for wreq-js in Codex WS transport (#12491) (#13756)

Merged in the 2026-09-16 sweep of the maintainer's own open PRs, at the owner's explicit instruction. No push was made to the PR branch: the merge took the head as the owning session left it (verified OPEN, non-draft and MERGEABLE against the release tip immediately before merging).
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-09-16 06:17:57 -03:00
committed by GitHub
parent 5ff85c6db6
commit 8939ccbae3
5 changed files with 84 additions and 2 deletions

View File

@@ -0,0 +1 @@
- **fix(sse):** Codex WebSocket transport (including the app-server) no longer fails to load in the Next.js standalone Docker runtime — the wreq-js loader now resolves its module name dynamically instead of a literal Turbopack could rewrite to an unreachable build-time symlink (#12491) — thanks @marshalfevzi

View File

@@ -43,6 +43,7 @@ import { errorResponse } from "../utils/error.ts";
import { normalizeCodexResponsesInput } from "../utils/responsesInputNormalization.ts";
import * as prl from "../utils/providerRequestLogging.ts";
import { createRequire } from "module";
import { loadDynamicModule } from "./codex/wreqLoader.ts";
// Quota parsing/scheduling extracted to a pure leaf; re-exported for the
// Codex account module and tests.
export {
@@ -90,7 +91,7 @@ function getCodexWebSocketTransport(): WebsocketFn | null {
if (_wreqChecked) return _websocketFn;
_wreqChecked = true;
try {
const mod = _wreqRequire("wreq-js") as { websocket?: WebsocketFn };
const mod = loadDynamicModule(_wreqRequire, "wreq-js") as { websocket?: WebsocketFn };
_websocketFn = typeof mod.websocket === "function" ? mod.websocket : null;
} catch {
console.warn("[codex] wreq-js import failed, websocket disabled");

View File

@@ -0,0 +1,9 @@
// #12491 — keep the module-name argument dynamic (never a literal string)
// when calling a createRequire()-returned function. Turbopack statically
// detects a literal specifier and rewrites the call to a hashed require()
// target that resolves only via a `.next`-relative symlink generated at
// build time, which is absent from the standalone Docker runtime. Mirrors
// open-sse/utils/tlsClient.ts's loadRuntimeModule().
export function loadDynamicModule(requireFn: NodeRequire, moduleName: string): unknown {
return Reflect.apply(requireFn, undefined, [moduleName]);
}

View File

@@ -5,7 +5,13 @@ import { getTlsClientTimeoutConfig } from "@/shared/utils/runtimeTimeouts";
// import the first-byte watchdog alongside TlsClient without adding a line.
export { guardTlsFirstByte } from "./tlsFirstByteWatchdog.ts";
const runtimeRequire = nodeModule.createRequire(import.meta.url);
// #12491 — anchor on process.argv[1]||cwd() rather than import.meta.url: the
// standalone Docker runtime re-lays-out files at a different relative depth
// than the build, so an import.meta.url-relative resolution can miss even
// though this loader already keeps the specifier itself dynamic (see
// loadRuntimeModule() below). Matches src/lib/machineToken.ts and
// src/lib/db/adapters/runtimeRequire.ts's established anchor pattern.
const runtimeRequire = nodeModule.createRequire(process.argv[1] || process.cwd());
function loadRuntimeModule(moduleName: string): unknown {
// Keep the specifier dynamic. Turbopack rewrites a literal createRequire call

View File

@@ -0,0 +1,65 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { dirname, join } from "node:path";
import test from "node:test";
import { fileURLToPath } from "node:url";
const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
/**
* Regression guard for #12491: the Codex app-server WebSocket transport
* ("Codex app-server websocket transport unavailable") never comes up inside
* the Next.js standalone Docker runtime because open-sse/executors/codex.ts
* resolves wreq-js with a LITERAL specifier:
*
* const _wreqRequire = createRequire(import.meta.url);
* const mod = _wreqRequire("wreq-js");
*
* Turbopack statically detects the literal string argument and rewrites the
* call into `require("wreq-js-<buildhash>")`, satisfied only by a symlink
* Turbopack drops at `.next/node_modules/wreq-js-<buildhash> -> node_modules/wreq-js`
* during the build. That symlink is a `.next`-relative build artifact; it is not
* reachable through the same relative path once files are re-laid-out for a
* standalone Docker image, so `require("wreq-js-<buildhash>")` throws
* MODULE_NOT_FOUND at runtime — even though `node -e "require('wreq-js')"`
* (the literal package name) succeeds fine in the very same container.
*
* Verified interactively against a real `next build --turbopack` (Next 16.3.3):
* compiling `const _wreqRequire = createRequire(import.meta.url); _wreqRequire("wreq-js")`
* emits `64301,(e,t,r)=>{t.exports=e.x("wreq-js-3b69dd5e46bd26d3",()=>require("wreq-js-3b69dd5e46bd26d3"))}`
* in the compiled chunk, and the build directory gained
* `.next/node_modules/wreq-js-3b69dd5e46bd26d3 -> ../../node_modules/wreq-js`.
* `open-sse/utils/tlsClient.ts` (see its `loadRuntimeModule()`) was already
* hardened against exactly this by keeping the specifier a runtime variable
* (`Reflect.apply(runtimeRequire, undefined, [moduleName])`), which Turbopack
* cannot statically rewrite — codex.ts's own wreq-js loader (the one that feeds
* `getCodexAppServerWebsocketTransport()` / `CodexAppServerClient.connect()`)
* never received the same treatment.
*/
const CODEX_TS_PATH = join(ROOT, "open-sse", "executors", "codex.ts");
test("codex.ts must not pass a literal specifier to the wreq-js createRequire() loader", () => {
const source = readFileSync(CODEX_TS_PATH, "utf8");
assert.match(
source,
/const _wreqRequire = createRequire\(import\.meta\.url\)/,
"expected open-sse/executors/codex.ts to still define _wreqRequire via createRequire(import.meta.url) " +
"— update this test's assumptions if the loader was restructured"
);
const literalCallPattern = /_wreqRequire\(\s*["'`]wreq-js["'`]\s*\)/;
assert.doesNotMatch(
source,
literalCallPattern,
'open-sse/executors/codex.ts calls _wreqRequire("wreq-js") with a LITERAL specifier. ' +
'Turbopack statically rewrites this into `require("wreq-js-<buildhash>")`, which only ' +
"resolves via a `.next/node_modules/wreq-js-<buildhash>` symlink generated at build time — " +
"not reachable in the standalone Docker runtime, so the Codex app-server WebSocket transport " +
"(and the plain Codex WS transport sharing this loader) is permanently disabled in production " +
"(#12491). Route the specifier through a variable the bundler cannot statically analyze, " +
"e.g. Reflect.apply(_wreqRequire, undefined, [moduleName]) — the exact pattern already used by " +
"open-sse/utils/tlsClient.ts's loadRuntimeModule()."
);
});