From 0a710bd926ef2631106caaf9798433739ac28241 Mon Sep 17 00:00:00 2001 From: Michael D'Angelo Date: Sun, 2 Aug 2026 20:36:15 -0700 Subject: [PATCH 1/7] fix: reset stale deep-scan runtime configuration --- sdk/typescript/src/api.ts | 8 ++- sdk/typescript/tests-ts/api.test.ts | 103 ++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+), 2 deletions(-) diff --git a/sdk/typescript/src/api.ts b/sdk/typescript/src/api.ts index 888431f4..fcc19935 100644 --- a/sdk/typescript/src/api.ts +++ b/sdk/typescript/src/api.ts @@ -1428,9 +1428,13 @@ async function prepareDeepScanConfig( const value = options[name]; if (value !== undefined) overrides[key] = value; } - if (existing === undefined && Object.keys(overrides).length === 0) return; const destination = join(codexHome, "codex-security", "config.toml"); - if (destination === source && Object.keys(overrides).length === 0) return; + const hasOverrides = Object.keys(overrides).length > 0; + if (existing === undefined && !hasOverrides) { + if (destination !== source) await rm(destination, { force: true }); + return; + } + if (destination === source && !hasOverrides) return; await mkdir(dirname(destination), { recursive: true, mode: 0o700 }); await writeFile( destination, diff --git a/sdk/typescript/tests-ts/api.test.ts b/sdk/typescript/tests-ts/api.test.ts index 6f11f48b..0c90fe1f 100644 --- a/sdk/typescript/tests-ts/api.test.ts +++ b/sdk/typescript/tests-ts/api.test.ts @@ -1825,6 +1825,109 @@ describe("CodexSecurity orchestration", () => { await client.close(); }); + test.each(["removed", "without deep settings"] as const)( + "clears stale runtime deep-scan configuration when ambient settings are %s", + async (ambientState) => { + const root = await temporaryDirectory(); + const repository = join(root, "repository"); + const ambientHome = join(root, "ambient-home"); + const runtimeHome = join(root, "runtime-home"); + const scanDir = join(root, "scan"); + const ambientConfig = join(ambientHome, "codex-security", "config.toml"); + const runtimeConfig = join(runtimeHome, "codex-security", "config.toml"); + await mkdir(repository); + await mkdir(join(ambientHome, "codex-security"), { recursive: true }); + await mkdir(runtimeHome); + await mkdir(scanDir, { mode: 0o700 }); + await writeFile( + ambientConfig, + "[deep_scan]\nworkers = 5\n[other]\nenabled = true\n", + ); + + const client = new TestClient( + {}, + { + environment: { CODEX_HOME: ambientHome }, + prepareRuntime: async () => preparedRuntime(runtimeHome), + resolvePluginPython: async () => "/managed/python", + prepareOutputDir: async () => scanDir, + repositoryRevision: async () => "deadbeef", + createCodex: () => ({ + startThread: () => ({ + id: null, + async runStreamed() { + throw new Error("deep scan settings captured"); + }, + }), + }), + }, + ); + + await expect( + client.run(repository, { mode: "deep", workers: 2 }), + ).rejects.toThrow("deep scan settings captured"); + expect(await readFile(runtimeConfig, "utf8")).toContain("workers = 2"); + + if (ambientState === "removed") { + await rm(ambientConfig); + } else { + await writeFile(ambientConfig, "[other]\nenabled = true\n"); + } + + await expect(client.run(repository, { mode: "deep" })).rejects.toThrow( + "deep scan settings captured", + ); + await expect(readFile(runtimeConfig, "utf8")).rejects.toMatchObject({ + code: "ENOENT", + }); + + await writeFile(ambientConfig, "[deep_scan]\nworkers = 7\n"); + await expect(client.run(repository, { mode: "deep" })).rejects.toThrow( + "deep scan settings captured", + ); + expect(await readFile(runtimeConfig, "utf8")).toContain("workers = 7"); + await client.close(); + }, + ); + + test("preserves ambient configuration when the deep-scan runtime shares its home", async () => { + const root = await temporaryDirectory(); + const repository = join(root, "repository"); + const codexHome = join(root, "codex-home"); + const scanDir = join(root, "scan"); + const configPath = join(codexHome, "codex-security", "config.toml"); + const originalConfiguration = "[other]\nenabled = true\n"; + await mkdir(repository); + await mkdir(join(codexHome, "codex-security"), { recursive: true }); + await mkdir(scanDir, { mode: 0o700 }); + await writeFile(configPath, originalConfiguration); + + const client = new TestClient( + {}, + { + environment: { CODEX_HOME: codexHome }, + prepareRuntime: async () => preparedRuntime(codexHome), + resolvePluginPython: async () => "/managed/python", + prepareOutputDir: async () => scanDir, + repositoryRevision: async () => "deadbeef", + createCodex: () => ({ + startThread: () => ({ + id: null, + async runStreamed() { + throw new Error("deep scan settings captured"); + }, + }), + }), + }, + ); + + await expect(client.run(repository, { mode: "deep" })).rejects.toThrow( + "deep scan settings captured", + ); + expect(await readFile(configPath, "utf8")).toBe(originalConfiguration); + await client.close(); + }); + test("rejects a scan registration without an authoritative target contract", async () => { const root = await temporaryDirectory(); const repository = join(root, "repository"); From 7e4b18cfe1f424f4a539186d6e20bdc9e0061364 Mon Sep 17 00:00:00 2001 From: Michael D'Angelo Date: Sun, 2 Aug 2026 23:04:46 -0700 Subject: [PATCH 2/7] fix: preserve config under canonical home aliases --- sdk/typescript/src/api.ts | 12 ++++- sdk/typescript/tests-ts/api.test.ts | 82 +++++++++++++++++------------ 2 files changed, 59 insertions(+), 35 deletions(-) diff --git a/sdk/typescript/src/api.ts b/sdk/typescript/src/api.ts index fcc19935..02426836 100644 --- a/sdk/typescript/src/api.ts +++ b/sdk/typescript/src/api.ts @@ -1431,7 +1431,17 @@ async function prepareDeepScanConfig( const destination = join(codexHome, "codex-security", "config.toml"); const hasOverrides = Object.keys(overrides).length > 0; if (existing === undefined && !hasOverrides) { - if (destination !== source) await rm(destination, { force: true }); + if (destination !== source) { + const canonicalAmbientHome = await realpath(ambientHome).catch( + (error: unknown) => { + if (isRecord(error) && error["code"] === "ENOENT") return null; + throw error; + }, + ); + if (canonicalAmbientHome !== codexHome) { + await rm(destination, { force: true }); + } + } return; } if (destination === source && !hasOverrides) return; diff --git a/sdk/typescript/tests-ts/api.test.ts b/sdk/typescript/tests-ts/api.test.ts index 0c90fe1f..d2e80ba7 100644 --- a/sdk/typescript/tests-ts/api.test.ts +++ b/sdk/typescript/tests-ts/api.test.ts @@ -1890,43 +1890,57 @@ describe("CodexSecurity orchestration", () => { }, ); - test("preserves ambient configuration when the deep-scan runtime shares its home", async () => { - const root = await temporaryDirectory(); - const repository = join(root, "repository"); - const codexHome = join(root, "codex-home"); - const scanDir = join(root, "scan"); - const configPath = join(codexHome, "codex-security", "config.toml"); - const originalConfiguration = "[other]\nenabled = true\n"; - await mkdir(repository); - await mkdir(join(codexHome, "codex-security"), { recursive: true }); - await mkdir(scanDir, { mode: 0o700 }); - await writeFile(configPath, originalConfiguration); + test.each(["the same path", "a symlink alias"] as const)( + "preserves ambient configuration when the deep-scan runtime shares its home through %s", + async (homeAlias) => { + const root = await temporaryDirectory(); + const repository = join(root, "repository"); + const codexHome = join(root, "codex-home"); + const ambientHome = + homeAlias === "a symlink alias" + ? join(root, "ambient-home-link") + : codexHome; + const scanDir = join(root, "scan"); + const configPath = join(codexHome, "codex-security", "config.toml"); + const originalConfiguration = "[other]\nenabled = true\n"; + await mkdir(repository); + await mkdir(join(codexHome, "codex-security"), { recursive: true }); + if (ambientHome !== codexHome) { + await symlink( + codexHome, + ambientHome, + process.platform === "win32" ? "junction" : "dir", + ); + } + await mkdir(scanDir, { mode: 0o700 }); + await writeFile(configPath, originalConfiguration); - const client = new TestClient( - {}, - { - environment: { CODEX_HOME: codexHome }, - prepareRuntime: async () => preparedRuntime(codexHome), - resolvePluginPython: async () => "/managed/python", - prepareOutputDir: async () => scanDir, - repositoryRevision: async () => "deadbeef", - createCodex: () => ({ - startThread: () => ({ - id: null, - async runStreamed() { - throw new Error("deep scan settings captured"); - }, + const client = new TestClient( + {}, + { + environment: { CODEX_HOME: ambientHome }, + prepareRuntime: async () => preparedRuntime(codexHome), + resolvePluginPython: async () => "/managed/python", + prepareOutputDir: async () => scanDir, + repositoryRevision: async () => "deadbeef", + createCodex: () => ({ + startThread: () => ({ + id: null, + async runStreamed() { + throw new Error("deep scan settings captured"); + }, + }), }), - }), - }, - ); + }, + ); - await expect(client.run(repository, { mode: "deep" })).rejects.toThrow( - "deep scan settings captured", - ); - expect(await readFile(configPath, "utf8")).toBe(originalConfiguration); - await client.close(); - }); + await expect(client.run(repository, { mode: "deep" })).rejects.toThrow( + "deep scan settings captured", + ); + expect(await readFile(configPath, "utf8")).toBe(originalConfiguration); + await client.close(); + }, + ); test("rejects a scan registration without an authoritative target contract", async () => { const root = await temporaryDirectory(); From 91d545b50c00a940a6c4b33210a7532c165a1cbb Mon Sep 17 00:00:00 2001 From: Michael D'Angelo Date: Sun, 2 Aug 2026 23:23:27 -0700 Subject: [PATCH 3/7] fix: compare canonical deep-scan config identities --- sdk/typescript/src/api.ts | 19 ++++++++++++------ sdk/typescript/tests-ts/api.test.ts | 30 +++++++++++++++++++++++------ 2 files changed, 37 insertions(+), 12 deletions(-) diff --git a/sdk/typescript/src/api.ts b/sdk/typescript/src/api.ts index 02426836..a1ad6c6a 100644 --- a/sdk/typescript/src/api.ts +++ b/sdk/typescript/src/api.ts @@ -1432,13 +1432,20 @@ async function prepareDeepScanConfig( const hasOverrides = Object.keys(overrides).length > 0; if (existing === undefined && !hasOverrides) { if (destination !== source) { - const canonicalAmbientHome = await realpath(ambientHome).catch( - (error: unknown) => { - if (isRecord(error) && error["code"] === "ENOENT") return null; - throw error; - }, + const [canonicalSource, canonicalDestination] = await Promise.all( + [source, destination].map(async (path) => { + try { + return await realpath(path); + } catch (error) { + if (isRecord(error) && error["code"] === "ENOENT") return null; + throw error; + } + }), ); - if (canonicalAmbientHome !== codexHome) { + if ( + canonicalDestination !== null && + canonicalSource !== canonicalDestination + ) { await rm(destination, { force: true }); } } diff --git a/sdk/typescript/tests-ts/api.test.ts b/sdk/typescript/tests-ts/api.test.ts index d2e80ba7..fa74d812 100644 --- a/sdk/typescript/tests-ts/api.test.ts +++ b/sdk/typescript/tests-ts/api.test.ts @@ -1890,14 +1890,19 @@ describe("CodexSecurity orchestration", () => { }, ); - test.each(["the same path", "a symlink alias"] as const)( - "preserves ambient configuration when the deep-scan runtime shares its home through %s", - async (homeAlias) => { + test.each([ + "the same path", + "a symlink alias", + "a shared configuration directory", + ...(process.platform === "win32" ? [] : ["a shared configuration file"]), + ])( + "preserves ambient configuration when the deep-scan runtime shares it through %s", + async (configurationAlias) => { const root = await temporaryDirectory(); const repository = join(root, "repository"); const codexHome = join(root, "codex-home"); const ambientHome = - homeAlias === "a symlink alias" + configurationAlias !== "the same path" ? join(root, "ambient-home-link") : codexHome; const scanDir = join(root, "scan"); @@ -1905,15 +1910,28 @@ describe("CodexSecurity orchestration", () => { const originalConfiguration = "[other]\nenabled = true\n"; await mkdir(repository); await mkdir(join(codexHome, "codex-security"), { recursive: true }); - if (ambientHome !== codexHome) { + await writeFile(configPath, originalConfiguration); + if (configurationAlias === "a symlink alias") { await symlink( codexHome, ambientHome, process.platform === "win32" ? "junction" : "dir", ); + } else if (configurationAlias === "a shared configuration directory") { + await mkdir(ambientHome); + await symlink( + join(codexHome, "codex-security"), + join(ambientHome, "codex-security"), + process.platform === "win32" ? "junction" : "dir", + ); + } else if (configurationAlias === "a shared configuration file") { + await mkdir(join(ambientHome, "codex-security"), { recursive: true }); + await symlink( + configPath, + join(ambientHome, "codex-security", "config.toml"), + ); } await mkdir(scanDir, { mode: 0o700 }); - await writeFile(configPath, originalConfiguration); const client = new TestClient( {}, From 386cec54cde8bd13a4087cfb2bc57e542ff222c5 Mon Sep 17 00:00:00 2001 From: Michael D'Angelo Date: Sun, 2 Aug 2026 23:39:07 -0700 Subject: [PATCH 4/7] fix: identify shared and dangling deep-scan configuration safely --- sdk/typescript/src/api.ts | 11 +++++++---- sdk/typescript/tests-ts/api.test.ts | 26 ++++++++++++++++++++++++-- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/sdk/typescript/src/api.ts b/sdk/typescript/src/api.ts index a1ad6c6a..0e871c0c 100644 --- a/sdk/typescript/src/api.ts +++ b/sdk/typescript/src/api.ts @@ -7,6 +7,7 @@ import { readFile, realpath, rm, + stat, writeFile, } from "node:fs/promises"; import { randomUUID } from "node:crypto"; @@ -1432,10 +1433,10 @@ async function prepareDeepScanConfig( const hasOverrides = Object.keys(overrides).length > 0; if (existing === undefined && !hasOverrides) { if (destination !== source) { - const [canonicalSource, canonicalDestination] = await Promise.all( + const [sourceIdentity, destinationIdentity] = await Promise.all( [source, destination].map(async (path) => { try { - return await realpath(path); + return await stat(path); } catch (error) { if (isRecord(error) && error["code"] === "ENOENT") return null; throw error; @@ -1443,8 +1444,10 @@ async function prepareDeepScanConfig( }), ); if ( - canonicalDestination !== null && - canonicalSource !== canonicalDestination + sourceIdentity == null || + destinationIdentity == null || + sourceIdentity.dev !== destinationIdentity.dev || + sourceIdentity.ino !== destinationIdentity.ino ) { await rm(destination, { force: true }); } diff --git a/sdk/typescript/tests-ts/api.test.ts b/sdk/typescript/tests-ts/api.test.ts index fa74d812..c002955c 100644 --- a/sdk/typescript/tests-ts/api.test.ts +++ b/sdk/typescript/tests-ts/api.test.ts @@ -1,6 +1,7 @@ import { copyFile, cp, + link, mkdir, mkdtemp, readFile, @@ -1825,7 +1826,13 @@ describe("CodexSecurity orchestration", () => { await client.close(); }); - test.each(["removed", "without deep settings"] as const)( + test.each([ + "removed", + "without deep settings", + ...(process.platform === "win32" + ? [] + : ["without deep settings and a dangling runtime link"]), + ])( "clears stale runtime deep-scan configuration when ambient settings are %s", async (ambientState) => { const root = await temporaryDirectory(); @@ -1835,6 +1842,7 @@ describe("CodexSecurity orchestration", () => { const scanDir = join(root, "scan"); const ambientConfig = join(ambientHome, "codex-security", "config.toml"); const runtimeConfig = join(runtimeHome, "codex-security", "config.toml"); + const escapedConfig = join(root, "escaped-config.toml"); await mkdir(repository); await mkdir(join(ambientHome, "codex-security"), { recursive: true }); await mkdir(runtimeHome); @@ -1873,11 +1881,17 @@ describe("CodexSecurity orchestration", () => { } else { await writeFile(ambientConfig, "[other]\nenabled = true\n"); } + if ( + ambientState === "without deep settings and a dangling runtime link" + ) { + await rm(runtimeConfig); + await symlink(escapedConfig, runtimeConfig); + } await expect(client.run(repository, { mode: "deep" })).rejects.toThrow( "deep scan settings captured", ); - await expect(readFile(runtimeConfig, "utf8")).rejects.toMatchObject({ + await expect(fsPromises.lstat(runtimeConfig)).rejects.toMatchObject({ code: "ENOENT", }); @@ -1886,6 +1900,7 @@ describe("CodexSecurity orchestration", () => { "deep scan settings captured", ); expect(await readFile(runtimeConfig, "utf8")).toContain("workers = 7"); + expect(existsSync(escapedConfig)).toBe(false); await client.close(); }, ); @@ -1894,6 +1909,7 @@ describe("CodexSecurity orchestration", () => { "the same path", "a symlink alias", "a shared configuration directory", + "a shared file identity", ...(process.platform === "win32" ? [] : ["a shared configuration file"]), ])( "preserves ambient configuration when the deep-scan runtime shares it through %s", @@ -1930,6 +1946,12 @@ describe("CodexSecurity orchestration", () => { configPath, join(ambientHome, "codex-security", "config.toml"), ); + } else if (configurationAlias === "a shared file identity") { + await mkdir(join(ambientHome, "codex-security"), { recursive: true }); + await link( + configPath, + join(ambientHome, "codex-security", "config.toml"), + ); } await mkdir(scanDir, { mode: 0o700 }); From 4fb7e92b2457acab4caca6cd896d99eb8552e141 Mon Sep 17 00:00:00 2001 From: Michael D'Angelo Date: Sun, 2 Aug 2026 23:46:55 -0700 Subject: [PATCH 5/7] fix: preserve shared dangling configuration links --- sdk/typescript/src/api.ts | 45 ++++++++++++++++++----------- sdk/typescript/tests-ts/api.test.ts | 23 ++++++++++++--- 2 files changed, 47 insertions(+), 21 deletions(-) diff --git a/sdk/typescript/src/api.ts b/sdk/typescript/src/api.ts index 0e871c0c..718027a9 100644 --- a/sdk/typescript/src/api.ts +++ b/sdk/typescript/src/api.ts @@ -1433,23 +1433,34 @@ async function prepareDeepScanConfig( const hasOverrides = Object.keys(overrides).length > 0; if (existing === undefined && !hasOverrides) { if (destination !== source) { - const [sourceIdentity, destinationIdentity] = await Promise.all( - [source, destination].map(async (path) => { - try { - return await stat(path); - } catch (error) { - if (isRecord(error) && error["code"] === "ENOENT") return null; - throw error; - } - }), - ); - if ( - sourceIdentity == null || - destinationIdentity == null || - sourceIdentity.dev !== destinationIdentity.dev || - sourceIdentity.ino !== destinationIdentity.ino - ) { - await rm(destination, { force: true }); + const identity = async (path: string, followSymlinks: boolean) => { + try { + return await (followSymlinks ? stat(path) : lstat(path)); + } catch (error) { + if (isRecord(error) && error["code"] === "ENOENT") return null; + throw error; + } + }; + const sameIdentity = ( + first: Awaited>, + second: Awaited>, + ): boolean => + first !== null && + second !== null && + first.dev === second.dev && + first.ino === second.ino; + const [sourceIdentity, destinationIdentity] = await Promise.all([ + identity(source, true), + identity(destination, true), + ]); + if (!sameIdentity(sourceIdentity, destinationIdentity)) { + const [sourceEntry, destinationEntry] = await Promise.all([ + identity(source, false), + identity(destination, false), + ]); + if (!sameIdentity(sourceEntry, destinationEntry)) { + await rm(destination, { force: true }); + } } } return; diff --git a/sdk/typescript/tests-ts/api.test.ts b/sdk/typescript/tests-ts/api.test.ts index c002955c..64e7e7f6 100644 --- a/sdk/typescript/tests-ts/api.test.ts +++ b/sdk/typescript/tests-ts/api.test.ts @@ -1910,7 +1910,9 @@ describe("CodexSecurity orchestration", () => { "a symlink alias", "a shared configuration directory", "a shared file identity", - ...(process.platform === "win32" ? [] : ["a shared configuration file"]), + ...(process.platform === "win32" + ? [] + : ["a shared configuration file", "a shared dangling configuration"]), ])( "preserves ambient configuration when the deep-scan runtime shares it through %s", async (configurationAlias) => { @@ -1926,14 +1928,21 @@ describe("CodexSecurity orchestration", () => { const originalConfiguration = "[other]\nenabled = true\n"; await mkdir(repository); await mkdir(join(codexHome, "codex-security"), { recursive: true }); - await writeFile(configPath, originalConfiguration); + if (configurationAlias === "a shared dangling configuration") { + await symlink(join(root, "missing-config.toml"), configPath); + } else { + await writeFile(configPath, originalConfiguration); + } if (configurationAlias === "a symlink alias") { await symlink( codexHome, ambientHome, process.platform === "win32" ? "junction" : "dir", ); - } else if (configurationAlias === "a shared configuration directory") { + } else if ( + configurationAlias === "a shared configuration directory" || + configurationAlias === "a shared dangling configuration" + ) { await mkdir(ambientHome); await symlink( join(codexHome, "codex-security"), @@ -1977,7 +1986,13 @@ describe("CodexSecurity orchestration", () => { await expect(client.run(repository, { mode: "deep" })).rejects.toThrow( "deep scan settings captured", ); - expect(await readFile(configPath, "utf8")).toBe(originalConfiguration); + if (configurationAlias === "a shared dangling configuration") { + expect((await fsPromises.lstat(configPath)).isSymbolicLink()).toBe( + true, + ); + } else { + expect(await readFile(configPath, "utf8")).toBe(originalConfiguration); + } await client.close(); }, ); From 4002aafbdfbd24f6e167aea5f31d8879d8d6497b Mon Sep 17 00:00:00 2001 From: Michael D'Angelo Date: Sun, 2 Aug 2026 23:53:49 -0700 Subject: [PATCH 6/7] fix: distinguish shared config entries from hard-linked symlinks --- sdk/typescript/src/api.ts | 16 ++++++++-------- sdk/typescript/tests-ts/api.test.ts | 22 +++++++++++++++++++++- 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/sdk/typescript/src/api.ts b/sdk/typescript/src/api.ts index 718027a9..6eae30c7 100644 --- a/sdk/typescript/src/api.ts +++ b/sdk/typescript/src/api.ts @@ -1433,9 +1433,9 @@ async function prepareDeepScanConfig( const hasOverrides = Object.keys(overrides).length > 0; if (existing === undefined && !hasOverrides) { if (destination !== source) { - const identity = async (path: string, followSymlinks: boolean) => { + const identity = async (path: string) => { try { - return await (followSymlinks ? stat(path) : lstat(path)); + return await stat(path); } catch (error) { if (isRecord(error) && error["code"] === "ENOENT") return null; throw error; @@ -1450,15 +1450,15 @@ async function prepareDeepScanConfig( first.dev === second.dev && first.ino === second.ino; const [sourceIdentity, destinationIdentity] = await Promise.all([ - identity(source, true), - identity(destination, true), + identity(source), + identity(destination), ]); if (!sameIdentity(sourceIdentity, destinationIdentity)) { - const [sourceEntry, destinationEntry] = await Promise.all([ - identity(source, false), - identity(destination, false), + const [sourceDirectory, destinationDirectory] = await Promise.all([ + identity(dirname(source)), + identity(dirname(destination)), ]); - if (!sameIdentity(sourceEntry, destinationEntry)) { + if (!sameIdentity(sourceDirectory, destinationDirectory)) { await rm(destination, { force: true }); } } diff --git a/sdk/typescript/tests-ts/api.test.ts b/sdk/typescript/tests-ts/api.test.ts index 64e7e7f6..82e03040 100644 --- a/sdk/typescript/tests-ts/api.test.ts +++ b/sdk/typescript/tests-ts/api.test.ts @@ -1831,7 +1831,10 @@ describe("CodexSecurity orchestration", () => { "without deep settings", ...(process.platform === "win32" ? [] - : ["without deep settings and a dangling runtime link"]), + : [ + "without deep settings and a dangling runtime link", + "without deep settings and a hard-linked dangling runtime link", + ]), ])( "clears stale runtime deep-scan configuration when ambient settings are %s", async (ambientState) => { @@ -1886,6 +1889,14 @@ describe("CodexSecurity orchestration", () => { ) { await rm(runtimeConfig); await symlink(escapedConfig, runtimeConfig); + } else if ( + ambientState === + "without deep settings and a hard-linked dangling runtime link" + ) { + await rm(ambientConfig); + await symlink(escapedConfig, ambientConfig); + await rm(runtimeConfig); + execFileSync("ln", ["-P", ambientConfig, runtimeConfig]); } await expect(client.run(repository, { mode: "deep" })).rejects.toThrow( @@ -1894,6 +1905,15 @@ describe("CodexSecurity orchestration", () => { await expect(fsPromises.lstat(runtimeConfig)).rejects.toMatchObject({ code: "ENOENT", }); + if ( + ambientState === + "without deep settings and a hard-linked dangling runtime link" + ) { + expect((await fsPromises.lstat(ambientConfig)).isSymbolicLink()).toBe( + true, + ); + await rm(ambientConfig); + } await writeFile(ambientConfig, "[deep_scan]\nworkers = 7\n"); await expect(client.run(repository, { mode: "deep" })).rejects.toThrow( From 045498552e05914cd26ac55cc9e1f42369f90f8c Mon Sep 17 00:00:00 2001 From: Michael D'Angelo Date: Mon, 3 Aug 2026 16:13:10 -0700 Subject: [PATCH 7/7] fix: simplify stale deep-scan configuration cleanup --- sdk/typescript/src/api.ts | 31 +----- sdk/typescript/tests-ts/api.test.ts | 141 ++++++++-------------------- 2 files changed, 38 insertions(+), 134 deletions(-) diff --git a/sdk/typescript/src/api.ts b/sdk/typescript/src/api.ts index 6eae30c7..4d0f01bf 100644 --- a/sdk/typescript/src/api.ts +++ b/sdk/typescript/src/api.ts @@ -7,7 +7,6 @@ import { readFile, realpath, rm, - stat, writeFile, } from "node:fs/promises"; import { randomUUID } from "node:crypto"; @@ -1433,35 +1432,7 @@ async function prepareDeepScanConfig( const hasOverrides = Object.keys(overrides).length > 0; if (existing === undefined && !hasOverrides) { if (destination !== source) { - const identity = async (path: string) => { - try { - return await stat(path); - } catch (error) { - if (isRecord(error) && error["code"] === "ENOENT") return null; - throw error; - } - }; - const sameIdentity = ( - first: Awaited>, - second: Awaited>, - ): boolean => - first !== null && - second !== null && - first.dev === second.dev && - first.ino === second.ino; - const [sourceIdentity, destinationIdentity] = await Promise.all([ - identity(source), - identity(destination), - ]); - if (!sameIdentity(sourceIdentity, destinationIdentity)) { - const [sourceDirectory, destinationDirectory] = await Promise.all([ - identity(dirname(source)), - identity(dirname(destination)), - ]); - if (!sameIdentity(sourceDirectory, destinationDirectory)) { - await rm(destination, { force: true }); - } - } + await rm(destination, { force: true }); } return; } diff --git a/sdk/typescript/tests-ts/api.test.ts b/sdk/typescript/tests-ts/api.test.ts index 82e03040..8cc9d342 100644 --- a/sdk/typescript/tests-ts/api.test.ts +++ b/sdk/typescript/tests-ts/api.test.ts @@ -1,7 +1,6 @@ import { copyFile, cp, - link, mkdir, mkdtemp, readFile, @@ -1833,7 +1832,7 @@ describe("CodexSecurity orchestration", () => { ? [] : [ "without deep settings and a dangling runtime link", - "without deep settings and a hard-linked dangling runtime link", + "without deep settings and a cyclic runtime link", ]), ])( "clears stale runtime deep-scan configuration when ambient settings are %s", @@ -1890,13 +1889,10 @@ describe("CodexSecurity orchestration", () => { await rm(runtimeConfig); await symlink(escapedConfig, runtimeConfig); } else if ( - ambientState === - "without deep settings and a hard-linked dangling runtime link" + ambientState === "without deep settings and a cyclic runtime link" ) { - await rm(ambientConfig); - await symlink(escapedConfig, ambientConfig); await rm(runtimeConfig); - execFileSync("ln", ["-P", ambientConfig, runtimeConfig]); + await symlink(runtimeConfig, runtimeConfig); } await expect(client.run(repository, { mode: "deep" })).rejects.toThrow( @@ -1905,15 +1901,6 @@ describe("CodexSecurity orchestration", () => { await expect(fsPromises.lstat(runtimeConfig)).rejects.toMatchObject({ code: "ENOENT", }); - if ( - ambientState === - "without deep settings and a hard-linked dangling runtime link" - ) { - expect((await fsPromises.lstat(ambientConfig)).isSymbolicLink()).toBe( - true, - ); - await rm(ambientConfig); - } await writeFile(ambientConfig, "[deep_scan]\nworkers = 7\n"); await expect(client.run(repository, { mode: "deep" })).rejects.toThrow( @@ -1925,97 +1912,43 @@ describe("CodexSecurity orchestration", () => { }, ); - test.each([ - "the same path", - "a symlink alias", - "a shared configuration directory", - "a shared file identity", - ...(process.platform === "win32" - ? [] - : ["a shared configuration file", "a shared dangling configuration"]), - ])( - "preserves ambient configuration when the deep-scan runtime shares it through %s", - async (configurationAlias) => { - const root = await temporaryDirectory(); - const repository = join(root, "repository"); - const codexHome = join(root, "codex-home"); - const ambientHome = - configurationAlias !== "the same path" - ? join(root, "ambient-home-link") - : codexHome; - const scanDir = join(root, "scan"); - const configPath = join(codexHome, "codex-security", "config.toml"); - const originalConfiguration = "[other]\nenabled = true\n"; - await mkdir(repository); - await mkdir(join(codexHome, "codex-security"), { recursive: true }); - if (configurationAlias === "a shared dangling configuration") { - await symlink(join(root, "missing-config.toml"), configPath); - } else { - await writeFile(configPath, originalConfiguration); - } - if (configurationAlias === "a symlink alias") { - await symlink( - codexHome, - ambientHome, - process.platform === "win32" ? "junction" : "dir", - ); - } else if ( - configurationAlias === "a shared configuration directory" || - configurationAlias === "a shared dangling configuration" - ) { - await mkdir(ambientHome); - await symlink( - join(codexHome, "codex-security"), - join(ambientHome, "codex-security"), - process.platform === "win32" ? "junction" : "dir", - ); - } else if (configurationAlias === "a shared configuration file") { - await mkdir(join(ambientHome, "codex-security"), { recursive: true }); - await symlink( - configPath, - join(ambientHome, "codex-security", "config.toml"), - ); - } else if (configurationAlias === "a shared file identity") { - await mkdir(join(ambientHome, "codex-security"), { recursive: true }); - await link( - configPath, - join(ambientHome, "codex-security", "config.toml"), - ); - } - await mkdir(scanDir, { mode: 0o700 }); + test("preserves ambient configuration when the deep-scan runtime uses the same home", async () => { + const root = await temporaryDirectory(); + const repository = join(root, "repository"); + const codexHome = join(root, "codex-home"); + const scanDir = join(root, "scan"); + const configPath = join(codexHome, "codex-security", "config.toml"); + const originalConfiguration = "[other]\nenabled = true\n"; + await mkdir(repository); + await mkdir(join(codexHome, "codex-security"), { recursive: true }); + await writeFile(configPath, originalConfiguration); + await mkdir(scanDir, { mode: 0o700 }); - const client = new TestClient( - {}, - { - environment: { CODEX_HOME: ambientHome }, - prepareRuntime: async () => preparedRuntime(codexHome), - resolvePluginPython: async () => "/managed/python", - prepareOutputDir: async () => scanDir, - repositoryRevision: async () => "deadbeef", - createCodex: () => ({ - startThread: () => ({ - id: null, - async runStreamed() { - throw new Error("deep scan settings captured"); - }, - }), + const client = new TestClient( + {}, + { + environment: { CODEX_HOME: codexHome }, + prepareRuntime: async () => preparedRuntime(codexHome), + resolvePluginPython: async () => "/managed/python", + prepareOutputDir: async () => scanDir, + repositoryRevision: async () => "deadbeef", + createCodex: () => ({ + startThread: () => ({ + id: null, + async runStreamed() { + throw new Error("deep scan settings captured"); + }, }), - }, - ); + }), + }, + ); - await expect(client.run(repository, { mode: "deep" })).rejects.toThrow( - "deep scan settings captured", - ); - if (configurationAlias === "a shared dangling configuration") { - expect((await fsPromises.lstat(configPath)).isSymbolicLink()).toBe( - true, - ); - } else { - expect(await readFile(configPath, "utf8")).toBe(originalConfiguration); - } - await client.close(); - }, - ); + await expect(client.run(repository, { mode: "deep" })).rejects.toThrow( + "deep scan settings captured", + ); + expect(await readFile(configPath, "utf8")).toBe(originalConfiguration); + await client.close(); + }); test("rejects a scan registration without an authoritative target contract", async () => { const root = await temporaryDirectory();