feat: route Codex hook commands through projection seam (#3444)

* feat: route Codex hook commands through projection seam

* docs: add changeset for Codex hook projection slice

* docs: set changeset pr for #3444

* fix(codex): decode toml-escaped legacy hook path

* test(codex): use structured assertion for hook projection
This commit is contained in:
Tom Boucher
2026-05-12 18:16:36 -04:00
committed by GitHub
parent 98cc1a84c0
commit 589318ce0a
4 changed files with 105 additions and 16 deletions

View File

@@ -37,7 +37,9 @@ const assert = require('node:assert/strict');
const path = require('node:path');
const INSTALL = require(path.join(__dirname, '..', 'bin', 'install.js'));
const projection = require(path.join(__dirname, '..', 'get-shit-done', 'bin', 'lib', 'shell-command-projection.cjs'));
const { buildCodexHookBlock, rewriteLegacyCodexHookBlock, resolveNodeRunner } = INSTALL;
const { projectCodexHookTomlCommand } = projection;
/**
* Parse the toml hook block into a typed record so tests can assert on
@@ -86,6 +88,20 @@ function unescapeRunner(token) {
return t;
}
describe('Bug #3017 / #3440: Codex hook projection seam', () => {
test('projectCodexHookTomlCommand renders escaped command value from shared projection module', () => {
const commandValue = projectCodexHookTomlCommand({
absoluteRunner: '"/usr/local/bin/node"',
scriptPath: '/tmp/codex-test/.codex/hooks/gsd-check-update.js',
platform: 'linux',
});
assert.equal(
commandValue,
'\\"/usr/local/bin/node\\" \\"/tmp/codex-test/.codex/hooks/gsd-check-update.js\\"',
);
});
});
describe('Bug #3017: buildCodexHookBlock emits absolute node runner', () => {
test('exported as a function', () => {
assert.equal(typeof buildCodexHookBlock, 'function');
@@ -169,6 +185,34 @@ describe('Bug #3017: rewriteLegacyCodexHookBlock migrates bare-node on reinstall
assert.ok(result.content.includes('name = "o3"'));
});
test('decodes TOML-escaped quoted script paths before projection', () => {
const before = [
'# GSD Hooks',
'[[hooks.SessionStart]]',
'',
'[[hooks.SessionStart.hooks]]',
'type = "command"',
'command = "node \\"C:\\\\Users\\\\x\\\\.codex\\\\hooks\\\\gsd-check-update.js\\""',
'',
].join('\n');
const runner = '"/usr/local/bin/node"';
const result = rewriteLegacyCodexHookBlock(before, runner, { platform: 'win32' });
assert.equal(result.changed, true);
const parsed = parseCodexHookBlock(result.content);
assert.equal(parsed.ok, true, 'hook block must parse correctly');
const expected = projectCodexHookTomlCommand({
absoluteRunner: runner,
scriptPath: 'C:\\Users\\x\\.codex\\hooks\\gsd-check-update.js',
platform: 'win32',
});
assert.equal(parsed.command, expected,
'rewritten command must project from decoded Windows path (not TOML-escaped token text)');
assert.equal(unescapeRunner(parsed.runner), '/usr/local/bin/node',
'runner must equal supplied absolute path');
assert.equal(parsed.hookPath, 'C:/Users/x/.codex/hooks/gsd-check-update.js',
'hook path must equal decoded Windows path after projection normalization');
});
test('does NOT touch a managed-hook entry that already uses an absolute runner', () => {
const already = [
'# GSD Hooks',