From 60ac74dd3118dc6a97a40d780024487137b8028e Mon Sep 17 00:00:00 2001 From: GOAT Date: Thu, 15 Oct 2026 20:50:29 +0900 Subject: [PATCH 1/3] fix(startup): skip unused native Codex ownership probes when disabled --- scripts/test-layout/layout.json | 1 + src/server/index.ts | 14 +++--- structure/runtime.md | 4 +- tests/fixtures/test-layout-expected.json | 1 + tests/server/server-startup-codex-off.test.ts | 47 +++++++++++++++++++ 5 files changed, 58 insertions(+), 9 deletions(-) create mode 100644 tests/server/server-startup-codex-off.test.ts diff --git a/scripts/test-layout/layout.json b/scripts/test-layout/layout.json index 16227fe5cf1..0259db7c482 100644 --- a/scripts/test-layout/layout.json +++ b/scripts/test-layout/layout.json @@ -1603,6 +1603,7 @@ "server-request-body-size.test.ts": "server", "server-search.test.ts": "server", "server-startup-reconcile-resilience.test.ts": "server", + "server-startup-codex-off.test.ts": "server", "server-stop-config-hardening.test.ts": "server", "server-xai-chat-reasoning-streaming.test.ts": "server", "server-xai-header-parity.test.ts": "server", diff --git a/src/server/index.ts b/src/server/index.ts index 1abb994f766..2b5d6bcc0fc 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -267,17 +267,17 @@ function startServerWithSpendLedgerOwner(port: number | undefined, deps: StartSe startupOwnershipHomes = homes; startupOwnershipStatePaths = statePaths; } catch { /* inspection below stays unknown */ } - const startupCacheOwnership = inspectStartupOwnership( + const startupCacheOwnership = shouldSyncCodexOnStart(config) ? inspectStartupOwnership( deps, startupOwnershipHomes, startupOwnershipStatePaths, startupWindowsTaskListingCache, - ); + ) : null; // Startup cache invalidation is best-effort and applies only when startup sync is enabled. // Check OFF before taking K: on Windows K resolves SID + LocalAppData through PowerShell, // and run 35093667426 exceeded healthy controls by 33.8 s against that 30 s child budget. // The permit callback still re-reads intent under K to close a concurrent disable race. - if (shouldSyncCodexOnStart(config) && startupCacheOwnership.ownership === "owned" && startupOwnershipHomes !== null) { + if (startupCacheOwnership?.ownership === "owned" && startupOwnershipHomes !== null) { try { const startupCodexHome = startupOwnershipHomes.codexHome; // #1046: record whether this actually rewrote the cache. `handleStart` ORs this @@ -549,12 +549,12 @@ function startServerWithSpendLedgerOwner(port: number | undefined, deps: StartSe // clients; no Codex request can use this lifecycle in that state. // Re-probe here instead of trusting the earlier cache decision: startup work // between the two sites must not widen the service-install race. - const nativeOwnership = inspectStartupOwnership( + const nativeOwnership = shouldSyncCodexOnStart(config) ? inspectStartupOwnership( deps, startupOwnershipHomes, startupOwnershipStatePaths, - ); - const preparedNativeMainLifecycle = nativeOwnership.ownership !== "foreign" + ) : null; + const preparedNativeMainLifecycle = nativeOwnership !== null && nativeOwnership.ownership !== "foreign" && startupOwnershipHomes !== null ? prepareNativeMainStartupLifecycle( deps.nativeMainStartup, @@ -600,7 +600,7 @@ function startServerWithSpendLedgerOwner(port: number | undefined, deps: StartSe return retryPreparedNativeMainLifecycle.start(); }, }; - const nativeMainLifecycle: NativeMainStartupLifecycle = shouldSyncCodexOnStart(config) + const nativeMainLifecycle: NativeMainStartupLifecycle = nativeOwnership !== null ? nativeOwnership.ownership === "owned" ? preparedNativeMainLifecycle ? preparedNativeMainLifecycle.start() diff --git a/structure/runtime.md b/structure/runtime.md index 2a31215cb52..85f788f2636 100644 --- a/structure/runtime.md +++ b/structure/runtime.md @@ -220,8 +220,8 @@ independent. Ordinary stop drains the spend reporters after listener teardown be reference. `src/server/index/spend-ledger-lifecycle.ts` also holds the lease through failed-start listener rollback. Enforced seeds remain live until reporters close and settlement plus send-ID forgetting are durable; scope totals survive forgetting. SQLite and the OS release a crashed owner; no PID, timestamp, TTL or lock-file deletion participates in recovery. Startup passes the provider roster and top-level pool aliases with the spend policy; live state reconciliation refreshes both without opening an unused ledger. Under the [request-start policy](transports/responses-spend.md#durable-spend-reservations), existing requests retain their initial enforcement mode, numeric ceilings and L; changed limits apply to later requests. HTTP admission checks [historical continuity](transports/responses-spend.md#historical-pool-continuity-and-rollback) even without a root; failed accounting reads release the active-turn lease. Rooted continuity denials record exactly one workflow refusal event before response formatting. -An explicit Codex integration OFF skips startup cache invalidation before the user-scoped catalog -serialization lock is resolved. Explicit `sync` and `sync-cache` retain their catalog-only override. +An explicit Codex integration OFF skips startup ownership probes, native-main preparation and cache invalidation before resolving the catalog lock. +Enabled startup retains separate fresh ownership decisions. Explicit `sync` and `sync-cache` retain their catalog-only override. `startServer` composes up to four sockets in one synchronous startup transaction: the public data listener, optional unauthenticated data-loopback and hub-management listeners, and the optional `hub-link` listener, which opens only for a recorded link and persists its concrete `127.0.0.1:`. HTTP Responses/Messages apply the [caller header contract](data-planes/inbound-compat.md#http-caller-conversation-identity) before turn admission and dispatch. Linked-machine data uses the [connection-bound relay contract](remote-link.md#connection-bound-relay-authentication); client-local credentials and routing policy remain unchanged. The data-loopback socket serves a fixed data-plane allowlist: Responses and its compact sibling, diff --git a/tests/fixtures/test-layout-expected.json b/tests/fixtures/test-layout-expected.json index 210221f4d50..634dce05e6e 100644 --- a/tests/fixtures/test-layout-expected.json +++ b/tests/fixtures/test-layout-expected.json @@ -1052,6 +1052,7 @@ "server-opencode-go-goal-streaming.test.ts": "server", "server-rate-limit-retry-e2e.test.ts": "server", "server-request-body-size.test.ts": "server", "server-search.test.ts": "server", "server-startup-reconcile-resilience.test.ts": "server", "server-stop-config-hardening.test.ts": "server", + "server-startup-codex-off.test.ts": "server", "server-xai-chat-reasoning-streaming.test.ts": "server", "server-xai-header-parity.test.ts": "server", "server-xai-oauth-401-replay.test.ts": "server", "server-xai-responses-streaming.test.ts": "server", "service-auth-qualified-localhost.test.ts": "service", "service-child-ownership.test.ts": "service", diff --git a/tests/server/server-startup-codex-off.test.ts b/tests/server/server-startup-codex-off.test.ts new file mode 100644 index 00000000000..699ce9421b5 --- /dev/null +++ b/tests/server/server-startup-codex-off.test.ts @@ -0,0 +1,47 @@ +import { expect, test } from "bun:test"; +import { mkdtempSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { saveConfig } from "../../src/config"; +import { startServer } from "../../src/server"; +import { installIsolatedCodexHome } from "../helpers/isolated-codex-home"; +import { removeTreeWithRetry } from "../helpers/remove-tree"; + +test("Codex OFF starts without service-manager probes or native-main preparation", async () => { + const priorHome = process.env.OPENCODEX_HOME; + const home = mkdtempSync(join(tmpdir(), "ocx-start-codex-off-")); + const codex = installIsolatedCodexHome("ocx-start-codex-off-client-"); + process.env.OPENCODEX_HOME = home; + let inspections = 0; + let preparations = 0; + let server: ReturnType | undefined; + try { + saveConfig({ + providers: { fixture: { adapter: "openai-chat", baseUrl: "http://127.0.0.1:1/v1", allowPrivateNetwork: true } }, + defaultProvider: "fixture", port: 0, hostname: "127.0.0.1", + clientIntegrations: { codex: false }, checkForUpdates: false, + syncResumeHistory: false, claudeCode: { intercept: { enabled: false } }, + }); + server = startServer(0, { + inspectNativeCodexOwnership: () => { + inspections++; + return { ownership: "owned", reason: "unused Codex OFF probe" }; + }, + nativeMainStartup: { + get manager() { + preparations++; + throw new Error("Codex OFF must not prepare a native manager"); + }, + }, + }); + expect(inspections).toBe(0); + expect(preparations).toBe(0); + expect((await fetch(`http://127.0.0.1:${server.port}/healthz`)).status).toBe(200); + } finally { + await server?.stop(true); + codex.restore(); + if (priorHome === undefined) delete process.env.OPENCODEX_HOME; + else process.env.OPENCODEX_HOME = priorHome; + removeTreeWithRetry(home); + } +}); From c85922a15937bf4d314853f080de6a5d2a73a42a Mon Sep 17 00:00:00 2001 From: GOAT Date: Thu, 15 Oct 2026 20:52:24 +0900 Subject: [PATCH 2/3] test(startup): exercise fresh ownership snapshots with Codex enabled --- .../codex-service-manager-probe-hardening.test.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/codex-integration/codex-service-manager-probe-hardening.test.ts b/tests/codex-integration/codex-service-manager-probe-hardening.test.ts index b209341df50..e671769aa2f 100644 --- a/tests/codex-integration/codex-service-manager-probe-hardening.test.ts +++ b/tests/codex-integration/codex-service-manager-probe-hardening.test.ts @@ -230,7 +230,7 @@ describe("Windows ownership probe hardening regressions", () => { ...getDefaultConfig(), port: 0, hostname: "127.0.0.1", - clientIntegrations: { codex: false }, + clientIntegrations: { codex: true }, subagentModels: [], }, null, 2)); @@ -278,7 +278,10 @@ describe("Windows ownership probe hardening regressions", () => { winswStatus: () => "nonexistent", }); ownerships.push(answer.ownership); - return answer; + // Exercise enabled startup's two real inspections without taking the unrelated + // user-scoped catalog lock (which resolves Windows identity through PowerShell). + // Preserve each raw verdict above so a stale second listing still fails this test. + return ownerships.length === 1 ? { ownership: "unknown", reason: "cache writes outside this fixture" } : answer; } finally { setTrustedWindowsSystemDirectoryResolverForTests(null); } From d0cdc26b235364121231cb5fcd2adbf02e093d39 Mon Sep 17 00:00:00 2001 From: JUN Date: Fri, 16 Oct 2026 17:45:48 +0900 Subject: [PATCH 3/3] test(codex): give bearer-admission server cases the server budget and settle teardown Each case starts a real server. On Windows the config-dir ACL hardening can exceed the 5 s default and still hold the home at teardown, which then cascades into spend-ledger owner conflicts. Use SERVER_BUDGET_MS for the file and removeTreeAfterBackgroundWork. --- .../bearer-admission-routed-provider.test.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/tests/codex-integration/bearer-admission-routed-provider.test.ts b/tests/codex-integration/bearer-admission-routed-provider.test.ts index 2ab77af91bd..21d036a0544 100644 --- a/tests/codex-integration/bearer-admission-routed-provider.test.ts +++ b/tests/codex-integration/bearer-admission-routed-provider.test.ts @@ -1,4 +1,4 @@ -import { afterEach, beforeEach, describe, expect, test } from "bun:test"; +import { afterEach, beforeEach, describe, expect, setDefaultTimeout, test } from "bun:test"; import { mkdtempSync, writeFileSync } from "node:fs"; import http2 from "node:http2"; import { tmpdir } from "node:os"; @@ -17,10 +17,13 @@ import { handleNativeProfileAPI } from "../../src/codex/native-profile-api"; import type { NativeProfileManager } from "../../src/codex/native-profile-manager"; import type { OcxConfig } from "../../src/types"; import { ownedServiceHomeInspection } from "../helpers/owned-service-home-inspection"; -import { removeTreeWithRetry } from "../helpers/remove-tree"; +import { removeTreeAfterBackgroundWork } from "../helpers/remove-tree"; import { fakeChatGptJwt } from "../helpers/fake-chatgpt-jwt"; import { resetVisionDescriptionCache } from "../../src/vision"; import { SERVER_BUDGET_MS } from "../helpers/test-budget"; + +// Every case starts a real server; on Windows its config-dir ACL work alone can exceed 5 s. +setDefaultTimeout(SERVER_BUDGET_MS); import { saveCodexAccountCredential } from "../../src/codex/account-store"; /** @@ -195,7 +198,7 @@ beforeEach(() => { }) as typeof fetch; }); -afterEach(() => { +afterEach(async () => { resetVisionDescriptionCache(); closeRequestHistoryIndex(); clearComboTargetCooldowns(); @@ -209,8 +212,9 @@ afterEach(() => { else process.env.CODEX_HOME = previousCodexHome; if (previousDataToken === undefined) delete process.env.OPENCODEX_API_AUTH_TOKEN; else process.env.OPENCODEX_API_AUTH_TOKEN = previousDataToken; - if (ocxHome) removeTreeWithRetry(ocxHome); - if (codexHome) removeTreeWithRetry(codexHome); + // Startup's config-dir ACL hardening may still hold the homes on Windows; settle it first. + if (ocxHome) await removeTreeAfterBackgroundWork(ocxHome); + if (codexHome) await removeTreeAfterBackgroundWork(codexHome); ocxHome = ""; codexHome = ""; });