From eb0d419b48c7c172eb10988d8c16f612f04b93f4 Mon Sep 17 00:00:00 2001 From: HarryZhou <2373256746@qq.com> Date: Wed, 5 Aug 2026 02:50:45 +0800 Subject: [PATCH 1/2] fix(codex): honor tri-state fastMode in Codex config injection injectCodexConfig unconditionally wrote [features] fast_mode = true, even when fastMode: false strips service_tier on the wire and when the user's config opted out. Thread the tri-state fastMode through ensureFastModeFeature and buildProfileFile: - true -> fast_mode = true (unchanged) - false -> fast_mode = false (consistent with the wire strip) - unset -> preserve the user's fast_mode; do not add a [features] table Adds unit and integration regression tests for all three states and updates the integration guide (en/ja/ko/ru/zh-cn). --- .../content/docs/guides/codex-integration.md | 4 ++ .../docs/ja/guides/codex-integration.md | 4 ++ .../docs/ko/guides/codex-integration.md | 4 ++ .../docs/ru/guides/codex-integration.md | 4 ++ .../docs/zh-cn/guides/codex-integration.md | 3 ++ src/codex/inject.ts | 25 +++++---- tests/codex-inject-integration.test.ts | 53 +++++++++++++++++++ tests/codex-inject.test.ts | 11 ++++ 8 files changed, 99 insertions(+), 9 deletions(-) diff --git a/docs-site/src/content/docs/guides/codex-integration.md b/docs-site/src/content/docs/guides/codex-integration.md index a36427a64..337d7ff27 100644 --- a/docs-site/src/content/docs/guides/codex-integration.md +++ b/docs-site/src/content/docs/guides/codex-integration.md @@ -27,6 +27,10 @@ openai_base_url = "http://127.0.0.1:10100/v1" fast_mode = true ``` +The injected `fast_mode` follows the tri-state `fastMode` setting: `true` writes `fast_mode = true`, +`false` writes `fast_mode = false`, and unset leaves an existing `fast_mode` untouched without +adding a `[features]` table. + The proxy listens on port `10100` by default and serves `POST /v1/responses`, `POST /v1/responses/compact`, `POST /v1/images/generations`, `POST /v1/images/edits`, `GET /v1/models`, `GET /healthz`, and the `/api/*` management surface. diff --git a/docs-site/src/content/docs/ja/guides/codex-integration.md b/docs-site/src/content/docs/ja/guides/codex-integration.md index 31f89510f..ec3604902 100644 --- a/docs-site/src/content/docs/ja/guides/codex-integration.md +++ b/docs-site/src/content/docs/ja/guides/codex-integration.md @@ -21,6 +21,10 @@ openai_base_url = "http://127.0.0.1:10100/v1" fast_mode = true ``` +注入される `fast_mode` は三値の `fastMode` 設定に従います。`true` は `fast_mode = true` を書き込み、 +`false` は `fast_mode = false` を書き込み、未設定の場合は既存の `fast_mode` を変更せずに +`[features]` テーブルも追加しません。 + プロキシはデフォルトでポート `10100` をリッスンし、`POST /v1/responses`、`POST /v1/responses/compact`、`POST /v1/images/generations`、`POST /v1/images/edits`、`GET /v1/models`、`GET /healthz`、および `/api/*` 管理サーフェスを提供します。 ### 組み込みの画像生成 (`image_gen`) diff --git a/docs-site/src/content/docs/ko/guides/codex-integration.md b/docs-site/src/content/docs/ko/guides/codex-integration.md index c87678eaa..37b7c4395 100644 --- a/docs-site/src/content/docs/ko/guides/codex-integration.md +++ b/docs-site/src/content/docs/ko/guides/codex-integration.md @@ -21,6 +21,10 @@ openai_base_url = "http://127.0.0.1:10100/v1" fast_mode = true ``` +주입되는 `fast_mode`는 3-상태 `fastMode` 설정을 따릅니다. `true`면 `fast_mode = true`를 쓰고, +`false`면 `fast_mode = false`를 쓰며, 설정하지 않으면 기존 `fast_mode`를 그대로 두고 +`[features]` 테이블도 추가하지 않습니다. + 프록시는 기본적으로 포트 `10100`에서 듣고 `POST /v1/responses`, `POST /v1/responses/compact`, `POST /v1/images/generations`, `POST /v1/images/edits`, `GET /v1/models`, `GET /healthz`, 그리고 `/api/*` 관리 표면을 제공합니다. ### 내장 이미지 생성 (`image_gen`) diff --git a/docs-site/src/content/docs/ru/guides/codex-integration.md b/docs-site/src/content/docs/ru/guides/codex-integration.md index 7445c84d4..c34128ce7 100644 --- a/docs-site/src/content/docs/ru/guides/codex-integration.md +++ b/docs-site/src/content/docs/ru/guides/codex-integration.md @@ -28,6 +28,10 @@ openai_base_url = "http://127.0.0.1:10100/v1" fast_mode = true ``` +Инжектируемый `fast_mode` следует трёхзначной настройке `fastMode`: `true` записывает +`fast_mode = true`, `false` — `fast_mode = false`, а при отсутствии настройки существующий +`fast_mode` сохраняется без изменений, и таблица `[features]` не добавляется. + Прокси по умолчанию слушает порт `10100` и обслуживает `POST /v1/responses`, `POST /v1/responses/compact`, `POST /v1/images/generations`, `POST /v1/images/edits`, `GET /v1/models`, `GET /healthz` и management surface `/api/*`. diff --git a/docs-site/src/content/docs/zh-cn/guides/codex-integration.md b/docs-site/src/content/docs/zh-cn/guides/codex-integration.md index d46ccfef9..9fa96fde9 100644 --- a/docs-site/src/content/docs/zh-cn/guides/codex-integration.md +++ b/docs-site/src/content/docs/zh-cn/guides/codex-integration.md @@ -27,6 +27,9 @@ openai_base_url = "http://127.0.0.1:10100/v1" fast_mode = true ``` +注入的 `fast_mode` 遵循三态 `fastMode` 配置:`true` 写入 `fast_mode = true`,`false` 写入 +`fast_mode = false`,未设置时保留用户已有的 `fast_mode` 且不添加 `[features]` 表。 + proxy 默认监听 `10100` 端口,并提供 `POST /v1/responses`、`POST /v1/responses/compact`、 `POST /v1/images/generations`、`POST /v1/images/edits`、`GET /v1/models`、`GET /healthz`, 以及 `/api/*` 管理面。 diff --git a/src/codex/inject.ts b/src/codex/inject.ts index 57d21e00a..2852db483 100644 --- a/src/codex/inject.ts +++ b/src/codex/inject.ts @@ -397,25 +397,32 @@ function normalizeServiceTier(content: string): string { return content.replace(/^(\s*service_tier\s*=\s*)["']priority["']\s*$/gm, '$1"fast"'); } -function ensureFastModeFeature(content: string): string { +function ensureFastModeFeature(content: string, fastMode?: boolean): string { + // Tri-state fast mode (see OcxConfig.fastMode): true forces `fast_mode = true`, + // false forces `fast_mode = false`, and undefined leaves the user's config + // untouched (no [features] table is added and an existing fast_mode line is + // preserved as-is). const lines = content.split("\n"); const featuresStart = lines.findIndex(line => line.trim() === "[features]"); if (featuresStart === -1) { - return content.trimEnd() + "\n\n[features]\nfast_mode = true\n"; + if (fastMode === undefined) return content; + return content.trimEnd() + "\n\n[features]\nfast_mode = " + (fastMode ? "true" : "false") + "\n"; } const nextTable = lines.findIndex((line, index) => index > featuresStart && /^\s*\[/.test(line)); const featuresEnd = nextTable === -1 ? lines.length : nextTable; for (let i = featuresStart + 1; i < featuresEnd; i++) { if (/^\s*fast_mode\s*=/.test(lines[i])) { - lines[i] = lines[i].replace(/^(\s*)fast_mode\s*=.*$/, "$1fast_mode = true"); + if (fastMode === undefined) return lines.join("\n"); + lines[i] = lines[i].replace(/^(\s*)fast_mode\s*=.*$/, `$1fast_mode = ${fastMode ? "true" : "false"}`); return lines.join("\n"); } } + if (fastMode === undefined) return lines.join("\n"); let insertAt = featuresEnd; while (insertAt > featuresStart + 1 && lines[insertAt - 1].trim() === "") insertAt--; - lines.splice(insertAt, 0, "fast_mode = true"); + lines.splice(insertAt, 0, `fast_mode = ${fastMode ? "true" : "false"}`); return lines.join("\n"); } @@ -433,7 +440,7 @@ function stripOpencodexCatalogPath(content: string): string { .join("\n"); } -export function buildProfileFile(port: number, catalogPath?: string | null, supportsWebsockets = false, includeApiAuthHeader = false, hostname?: string): string { +export function buildProfileFile(port: number, catalogPath?: string | null, supportsWebsockets = false, includeApiAuthHeader = false, hostname?: string, fastMode = true): string { const host = providerBaseHost(hostname); // Design B (loopback): the reference/fallback file documents the root override form. // Non-loopback keeps the legacy provider-table shape (built-in provider cannot carry @@ -446,7 +453,7 @@ export function buildProfileFile(port: number, catalogPath?: string | null, supp buildOpenaiBaseUrlLine(port, hostname), ]; if (catalogPath) lines.push(`model_catalog_json = ${tomlString(catalogPath)}`); - lines.push("", "[features]", "fast_mode = true", ""); + lines.push("", "[features]", `fast_mode = ${fastMode ? "true" : "false"}`, ""); return lines.join("\n"); } const lines = [ @@ -455,7 +462,7 @@ export function buildProfileFile(port: number, catalogPath?: string | null, supp 'model_provider = "opencodex"', ]; if (catalogPath) lines.push(`model_catalog_json = ${tomlString(catalogPath)}`); - lines.push("", "[features]", "fast_mode = true"); + lines.push("", "[features]", `fast_mode = ${fastMode ? "true" : "false"}`); lines.push(buildProviderTableBlock(port, supportsWebsockets, includeApiAuthHeader, hostname).trimEnd(), ""); return lines.join("\n"); } @@ -542,7 +549,7 @@ export async function injectCodexConfig(port: number, config?: OcxConfig, option content = stripExistingModelProvider(content); content = stripRootContextWindowOverrides(content); content = normalizeServiceTier(content); - content = ensureFastModeFeature(content); + content = ensureFastModeFeature(content, config?.fastMode); const catalogPath = chooseCatalogPathForInjection(content, options.catalogPath); content = catalogPath ? setRootModelCatalogPath(content, catalogPath) : stripOpencodexCatalogPath(content); @@ -590,7 +597,7 @@ export async function injectCodexConfig(port: number, config?: OcxConfig, option managedDefaultsMessage = ` ⚠️ ${nativeSubagentDefaultsWarning}\n`; } - const profileContent = buildProfileFile(port, catalogPath, websocketsEnabled(config ?? {}), legacyMode, config?.hostname); + const profileContent = buildProfileFile(port, catalogPath, websocketsEnabled(config ?? {}), legacyMode, config?.hostname, config?.fastMode); content = applyEol(content, eol); atomicWriteFile(CODEX_CONFIG_PATH, content); atomicWriteFile(CODEX_PROFILE_PATH, profileContent); diff --git a/tests/codex-inject-integration.test.ts b/tests/codex-inject-integration.test.ts index d66de5636..68bc0addd 100644 --- a/tests/codex-inject-integration.test.ts +++ b/tests/codex-inject-integration.test.ts @@ -100,6 +100,59 @@ describe("injectCodexConfig integration (Design B)", () => { expect(second).toBe(first); }); + test("fastMode=false forces fast_mode=false in both config and profile", () => { + writeFileSync(join(codexHome, "config.toml"), 'model = "gpt-5.5"\n', "utf8"); + + const r = runInject(codexHome, ocxHome, JSON.stringify({ fastMode: false })); + expect(r.status).toBe(0); + expect(JSON.parse(r.stdout).success).toBe(true); + + const config = readFileSync(join(codexHome, "config.toml"), "utf8"); + expect(config).toContain("[features]"); + expect(config).toContain("fast_mode = false"); + expect(config).not.toContain("fast_mode = true"); + + const profile = readFileSync(join(codexHome, "opencodex.config.toml"), "utf8"); + expect(profile).toContain("fast_mode = false"); + expect(profile).not.toContain("fast_mode = true"); + }); + + test("fastMode=true adds fast_mode=true to a config without a [features] table", () => { + writeFileSync(join(codexHome, "config.toml"), 'model = "gpt-5.5"\n', "utf8"); + + const r = runInject(codexHome, ocxHome, JSON.stringify({ fastMode: true })); + expect(r.status).toBe(0); + expect(JSON.parse(r.stdout).success).toBe(true); + + const config = readFileSync(join(codexHome, "config.toml"), "utf8"); + expect(config).toContain("[features]"); + expect(config).toContain("fast_mode = true"); + }); + + test("fastMode unset preserves the user's existing fast_mode setting", () => { + writeFileSync(join(codexHome, "config.toml"), 'model = "gpt-5.5"\n\n[features]\nfast_mode = false\n', "utf8"); + + const r = runInject(codexHome, ocxHome); + expect(r.status).toBe(0); + expect(JSON.parse(r.stdout).success).toBe(true); + + const config = readFileSync(join(codexHome, "config.toml"), "utf8"); + expect(config).toContain("fast_mode = false"); + expect(config).not.toContain("fast_mode = true"); + }); + + test("fastMode unset does not add a [features] table to a config that lacks one", () => { + writeFileSync(join(codexHome, "config.toml"), 'model = "gpt-5.5"\n', "utf8"); + + const r = runInject(codexHome, ocxHome); + expect(r.status).toBe(0); + expect(JSON.parse(r.stdout).success).toBe(true); + + const config = readFileSync(join(codexHome, "config.toml"), "utf8"); + expect(config).not.toContain("[features]"); + expect(config).not.toContain("fast_mode"); + }); + test("opt-in injects native subagent defaults, removes them when disabled, and restores the native config", () => { const original = [ 'model = "gpt-5.5"', diff --git a/tests/codex-inject.test.ts b/tests/codex-inject.test.ts index 775df524c..998b5215e 100644 --- a/tests/codex-inject.test.ts +++ b/tests/codex-inject.test.ts @@ -142,6 +142,17 @@ describe("Codex config injection", () => { expect(profile).toContain("fast_mode = true"); }); + test("fallback profile mirrors an explicit fastMode=false override", () => { + const loopback = buildProfileFile(10100, null, false, false, undefined, false); + + expect(loopback).toContain("fast_mode = false"); + expect(loopback).not.toContain("fast_mode = true"); + + const legacy = buildProfileFile(10100, null, false, true, "192.168.1.20", false); + expect(legacy).toContain("fast_mode = false"); + expect(legacy).not.toContain("fast_mode = true"); + }); + test("non-loopback fallback profile keeps the legacy provider-table shape with the injected host", () => { const profile = buildProfileFile(10100, null, false, true, "192.168.1.20"); From f742a3f3f79748298772be3d8d4f942b055a5354 Mon Sep 17 00:00:00 2001 From: HarryZhou <2373256746@qq.com> Date: Wed, 5 Aug 2026 03:04:39 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix(codex):=20review=20fixes=20=E2=80=94=20?= =?UTF-8?q?unset=20fastMode=20profile=20passthrough=20and=20TOML=20spellin?= =?UTF-8?q?gs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - buildProfileFile no longer defaults an unset fastMode to true: the fallback profile omits [features]/fast_mode when fastMode is unset, mirroring the config.toml passthrough instead of re-enabling priority mode. - ensureFastModeFeature recognizes valid TOML spellings ([features] # comment, ["features"]/['features'], quoted "fast_mode" keys) so an explicit fastMode updates the existing table/key rather than appending duplicates. - Integration tests assert the generated profile for all three states and cover the TOML spellings. --- src/codex/inject.ts | 17 ++++--- tests/codex-inject-integration.test.ts | 66 ++++++++++++++++++++++++++ tests/codex-inject.test.ts | 13 ++++- 3 files changed, 88 insertions(+), 8 deletions(-) diff --git a/src/codex/inject.ts b/src/codex/inject.ts index 2852db483..7ad937754 100644 --- a/src/codex/inject.ts +++ b/src/codex/inject.ts @@ -401,9 +401,12 @@ function ensureFastModeFeature(content: string, fastMode?: boolean): string { // Tri-state fast mode (see OcxConfig.fastMode): true forces `fast_mode = true`, // false forces `fast_mode = false`, and undefined leaves the user's config // untouched (no [features] table is added and an existing fast_mode line is - // preserved as-is). + // preserved as-is). Table and key matching accept the valid TOML spellings + // `[features] # comment`, `["features"]` / `['features']`, and quoted keys. const lines = content.split("\n"); - const featuresStart = lines.findIndex(line => line.trim() === "[features]"); + const featuresHeader = /^\s*\[(["']?)\s*features\s*\1\]\s*(?:#.*)?$/; + const fastModeKey = /^\s*(?:"fast_mode"|'fast_mode'|fast_mode)\s*=/; + const featuresStart = lines.findIndex(line => featuresHeader.test(line)); if (featuresStart === -1) { if (fastMode === undefined) return content; return content.trimEnd() + "\n\n[features]\nfast_mode = " + (fastMode ? "true" : "false") + "\n"; @@ -412,9 +415,9 @@ function ensureFastModeFeature(content: string, fastMode?: boolean): string { const nextTable = lines.findIndex((line, index) => index > featuresStart && /^\s*\[/.test(line)); const featuresEnd = nextTable === -1 ? lines.length : nextTable; for (let i = featuresStart + 1; i < featuresEnd; i++) { - if (/^\s*fast_mode\s*=/.test(lines[i])) { + if (fastModeKey.test(lines[i])) { if (fastMode === undefined) return lines.join("\n"); - lines[i] = lines[i].replace(/^(\s*)fast_mode\s*=.*$/, `$1fast_mode = ${fastMode ? "true" : "false"}`); + lines[i] = lines[i].replace(/^(\s*)(?:"fast_mode"|'fast_mode'|fast_mode)\s*=.*$/, `$1fast_mode = ${fastMode ? "true" : "false"}`); return lines.join("\n"); } } @@ -440,7 +443,7 @@ function stripOpencodexCatalogPath(content: string): string { .join("\n"); } -export function buildProfileFile(port: number, catalogPath?: string | null, supportsWebsockets = false, includeApiAuthHeader = false, hostname?: string, fastMode = true): string { +export function buildProfileFile(port: number, catalogPath?: string | null, supportsWebsockets = false, includeApiAuthHeader = false, hostname?: string, fastMode?: boolean): string { const host = providerBaseHost(hostname); // Design B (loopback): the reference/fallback file documents the root override form. // Non-loopback keeps the legacy provider-table shape (built-in provider cannot carry @@ -453,7 +456,7 @@ export function buildProfileFile(port: number, catalogPath?: string | null, supp buildOpenaiBaseUrlLine(port, hostname), ]; if (catalogPath) lines.push(`model_catalog_json = ${tomlString(catalogPath)}`); - lines.push("", "[features]", `fast_mode = ${fastMode ? "true" : "false"}`, ""); + if (fastMode !== undefined) lines.push("", "[features]", `fast_mode = ${fastMode ? "true" : "false"}`, ""); return lines.join("\n"); } const lines = [ @@ -462,7 +465,7 @@ export function buildProfileFile(port: number, catalogPath?: string | null, supp 'model_provider = "opencodex"', ]; if (catalogPath) lines.push(`model_catalog_json = ${tomlString(catalogPath)}`); - lines.push("", "[features]", `fast_mode = ${fastMode ? "true" : "false"}`); + if (fastMode !== undefined) lines.push("", "[features]", `fast_mode = ${fastMode ? "true" : "false"}`); lines.push(buildProviderTableBlock(port, supportsWebsockets, includeApiAuthHeader, hostname).trimEnd(), ""); return lines.join("\n"); } diff --git a/tests/codex-inject-integration.test.ts b/tests/codex-inject-integration.test.ts index 68bc0addd..a65ad66c3 100644 --- a/tests/codex-inject-integration.test.ts +++ b/tests/codex-inject-integration.test.ts @@ -127,6 +127,9 @@ describe("injectCodexConfig integration (Design B)", () => { const config = readFileSync(join(codexHome, "config.toml"), "utf8"); expect(config).toContain("[features]"); expect(config).toContain("fast_mode = true"); + + const profile = readFileSync(join(codexHome, "opencodex.config.toml"), "utf8"); + expect(profile).toContain("fast_mode = true"); }); test("fastMode unset preserves the user's existing fast_mode setting", () => { @@ -139,6 +142,9 @@ describe("injectCodexConfig integration (Design B)", () => { const config = readFileSync(join(codexHome, "config.toml"), "utf8"); expect(config).toContain("fast_mode = false"); expect(config).not.toContain("fast_mode = true"); + + const profile = readFileSync(join(codexHome, "opencodex.config.toml"), "utf8"); + expect(profile).not.toContain("fast_mode"); }); test("fastMode unset does not add a [features] table to a config that lacks one", () => { @@ -151,6 +157,66 @@ describe("injectCodexConfig integration (Design B)", () => { const config = readFileSync(join(codexHome, "config.toml"), "utf8"); expect(config).not.toContain("[features]"); expect(config).not.toContain("fast_mode"); + + const profile = readFileSync(join(codexHome, "opencodex.config.toml"), "utf8"); + expect(profile).not.toContain("fast_mode"); + }); + + test("fastMode=false updates a commented [features] header without duplicating the table", () => { + writeFileSync(join(codexHome, "config.toml"), [ + 'model = "gpt-5.5"', + "", + "[features] # user comment", + "fast_mode = true", + "", + ].join("\n"), "utf8"); + + const r = runInject(codexHome, ocxHome, JSON.stringify({ fastMode: false })); + expect(r.status).toBe(0); + + const config = readFileSync(join(codexHome, "config.toml"), "utf8"); + expect(config).toContain("fast_mode = false"); + expect(config).not.toContain("fast_mode = true"); + expect(() => Bun.TOML.parse(config)).not.toThrow(); + expect(Bun.TOML.parse(config).features.fast_mode).toBe(false); + }); + + test("fastMode=false updates a quoted [\"features\"] header without duplicating the table", () => { + writeFileSync(join(codexHome, "config.toml"), [ + 'model = "gpt-5.5"', + "", + '["features"]', + "fast_mode = true", + "", + ].join("\n"), "utf8"); + + const r = runInject(codexHome, ocxHome, JSON.stringify({ fastMode: false })); + expect(r.status).toBe(0); + + const config = readFileSync(join(codexHome, "config.toml"), "utf8"); + expect(config).toContain("fast_mode = false"); + expect(config).not.toContain("fast_mode = true"); + expect(() => Bun.TOML.parse(config)).not.toThrow(); + expect(Bun.TOML.parse(config).features.fast_mode).toBe(false); + }); + + test("fastMode=false updates a quoted \"fast_mode\" key", () => { + writeFileSync(join(codexHome, "config.toml"), [ + 'model = "gpt-5.5"', + "", + "[features]", + '"fast_mode" = true', + "", + ].join("\n"), "utf8"); + + const r = runInject(codexHome, ocxHome, JSON.stringify({ fastMode: false })); + expect(r.status).toBe(0); + + const config = readFileSync(join(codexHome, "config.toml"), "utf8"); + expect(config).toContain("fast_mode = false"); + expect(config).not.toContain("fast_mode = true"); + expect(() => Bun.TOML.parse(config)).not.toThrow(); + expect(Bun.TOML.parse(config).features.fast_mode).toBe(false); }); test("opt-in injects native subagent defaults, removes them when disabled, and restores the native config", () => { diff --git a/tests/codex-inject.test.ts b/tests/codex-inject.test.ts index 998b5215e..540830f8c 100644 --- a/tests/codex-inject.test.ts +++ b/tests/codex-inject.test.ts @@ -139,7 +139,18 @@ describe("Codex config injection", () => { expect(profile).not.toContain('model_provider = "opencodex"'); expect(profile).not.toContain("[model_providers.opencodex]"); expect(profile).not.toContain("model_catalog_json"); - expect(profile).toContain("fast_mode = true"); + }); + + test("fallback profile does not force fast_mode when fastMode is unset", () => { + expect(buildProfileFile(10100, null)).not.toContain("fast_mode"); + expect(buildProfileFile(10100, null, false, true, "192.168.1.20")).not.toContain("fast_mode"); + }); + + test("fallback profile mirrors an explicit fastMode=true override", () => { + const loopback = buildProfileFile(10100, null, false, false, undefined, true); + + expect(loopback).toContain("fast_mode = true"); + expect(loopback).not.toContain("fast_mode = false"); }); test("fallback profile mirrors an explicit fastMode=false override", () => {