test(codex): pin trust hash replacement during Windows upgrade

This commit is contained in:
Neil
2026-09-15 21:46:05 -07:00
parent 5eb6ac9df1
commit b6da1d3ff2
3 changed files with 20 additions and 15 deletions
+2 -10
View File
@@ -707,10 +707,7 @@ describe('wrapWindowsHookCommand', () => {
describe('wrapWindowsCmdHookCommand', () => {
it('returns the bare, directly-spawnable path for a cmd-safe managed script', () => {
// Why: Codex/Antigravity/Devin launch the command as a program (argv[0]),
// not via cmd.exe, so the launcher must be a single spawnable token — a bare
// .cmd path. A cmd-builtin `if …` launcher has argv[0] = `if`, which is
// unspawnable and fails every hook with exit 1 (#8430 regression).
// Direct-spawn consumers need a launchable argv[0], not a cmd builtin such as `if`.
const scriptPath = 'C:\\Users\\alice\\.orca\\agent-hooks\\codex-hook.cmd'
const command = wrapWindowsCmdHookCommand(scriptPath)
expect(command).toBe(scriptPath)
@@ -721,12 +718,7 @@ describe('wrapWindowsCmdHookCommand', () => {
it.skipIf(process.platform !== 'win32')(
'resolves the launcher to a real executable file, not a shell fragment',
() => {
// Regression guard for #8430: Codex/Antigravity/Devin spawn the launcher as
// a program (argv[0]), so it must be an existing, launchable file. The broken
// `if exist … (call …)` form had argv[0] = `if` — a cmd builtin, not a file —
// which is unspawnable and failed every hook. The bare path is the file.
// win32-only: the real temp path is cmd-safe only with backslashes; a POSIX
// tmpDir has `/`, which routes to the encoded fallback by design.
// POSIX temp paths contain `/`, which selects the encoded fallback instead.
const scriptPath = join(tmpDir, 'codex-hook.cmd')
writeFileSync(scriptPath, '@echo off\r\nexit /b 0\r\n', 'utf-8')
const command = wrapWindowsCmdHookCommand(scriptPath)
+1 -1
View File
@@ -74,7 +74,7 @@ export function getManagedCommand(scriptPath: string): string {
if (process.platform !== 'win32') {
return wrapPosixHookCommand(scriptPath)
}
// Codex already hosts Windows hooks in PowerShell; a second interpreter only adds startup cost.
// Codex's default native Windows hook host is PowerShell; reuse it to avoid a second interpreter.
return WINDOWS_CMD_SAFE_PATH.test(scriptPath)
? scriptPath
: buildWindowsHookPowerShellCommand(scriptPath)
+17 -4
View File
@@ -17,7 +17,11 @@ vi.mock('os', async (importOriginal) => ({
import { CodexHookService } from './hook-service'
import { CODEX_EVENTS, CODEX_EVENT_LABEL, getManagedCommand } from './codex-hook-definition'
import { readHooksJson, wrapWindowsHookCommand } from '../agent-hooks/installer-utils'
import { getCodexExplicitHomeHookSourcePath, upsertHookTrustEntries } from './config-toml-trust'
import {
computeTrustedHash,
getCodexExplicitHomeHookSourcePath,
upsertHookTrustEntries
} from './config-toml-trust'
const homes = setupCodexHookHomes(homedirMock, getPathMock)
@@ -57,27 +61,36 @@ describe.skipIf(process.platform !== 'win32')('Unicode Windows hook upgrade', ()
groupIndex: 0,
handlerIndex: 0,
command: oldCommand,
timeout: 10
timeoutSec: 10
}))
)
const oldTrust = readFileSync(tomlPath, 'utf8')
const service = new CodexHookService()
expect(service.getStatus().state).not.toBe('installed')
for (let pass = 0; pass < 2; pass++) {
expect((await service.install()).state).toBe('installed')
expect(service.getStatus().state).toBe('installed')
const hooks = readHooksJson(configPath)?.hooks
const trust = readFileSync(tomlPath, 'utf8')
for (const event of CODEX_EVENTS) {
const commands = hooks?.[event]?.flatMap((group) => group.hooks ?? []) ?? []
expect(
commands.filter((hook) => hook.command === getManagedCommand(scriptPath))
).toHaveLength(1)
expect(commands.some((hook) => hook.command === oldCommand)).toBe(false)
const entry = {
sourcePath: getCodexExplicitHomeHookSourcePath(configPath),
eventLabel: CODEX_EVENT_LABEL[event],
groupIndex: 0,
handlerIndex: 0,
command: getManagedCommand(scriptPath),
timeoutSec: 10
}
expect(trust).toContain(computeTrustedHash(entry))
expect(trust).not.toContain(computeTrustedHash({ ...entry, command: oldCommand }))
}
expect(
hooks?.Stop?.some((group) => group.hooks?.some((hook) => hook.command === 'user-hook'))
).toBe(true)
expect(readFileSync(tomlPath, 'utf8')).not.toBe(oldTrust)
expect(readFileSync(join(userHome, 'hooks.json'), 'utf8')).toBe(userConfig)
}
})