Codex install registered gsd-check-update.js in config.toml but never copied the hook file to ~/.codex/hooks/. The hook-copy block in install() was gated by !isCodex, leaving a broken reference on every fresh Codex global install. Adds a dedicated hook-copy step inside the isCodex branch that mirrors the existing copy logic (template substitution, chmod). Adds a regression test that verifies the hook file physically exists after install. Closes #2153 Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5856,6 +5856,35 @@ function install(isGlobal, runtime = 'claude') {
|
||||
console.log(` ${green}✓${reset} Generated config.toml with ${agentCount} agent roles`);
|
||||
console.log(` ${green}✓${reset} Generated ${agentCount} agent .toml config files`);
|
||||
|
||||
// Copy hook files that are referenced in config.toml (#2153)
|
||||
// The main hook-copy block is gated to non-Codex runtimes, but Codex registers
|
||||
// gsd-check-update.js in config.toml — the file must physically exist.
|
||||
const codexHooksSrc = path.join(src, 'hooks', 'dist');
|
||||
if (fs.existsSync(codexHooksSrc)) {
|
||||
const codexHooksDest = path.join(targetDir, 'hooks');
|
||||
fs.mkdirSync(codexHooksDest, { recursive: true });
|
||||
const configDirReplacement = getConfigDirFromHome(runtime, isGlobal);
|
||||
for (const entry of fs.readdirSync(codexHooksSrc)) {
|
||||
const srcFile = path.join(codexHooksSrc, entry);
|
||||
if (!fs.statSync(srcFile).isFile()) continue;
|
||||
const destFile = path.join(codexHooksDest, entry);
|
||||
if (entry.endsWith('.js')) {
|
||||
let content = fs.readFileSync(srcFile, 'utf8');
|
||||
content = content.replace(/'\.claude'/g, configDirReplacement);
|
||||
content = content.replace(/\/\.claude\//g, `/${getDirName(runtime)}/`);
|
||||
content = content.replace(/\{\{GSD_VERSION\}\}/g, pkg.version);
|
||||
fs.writeFileSync(destFile, content);
|
||||
try { fs.chmodSync(destFile, 0o755); } catch (e) { /* Windows */ }
|
||||
} else {
|
||||
fs.copyFileSync(srcFile, destFile);
|
||||
if (entry.endsWith('.sh')) {
|
||||
try { fs.chmodSync(destFile, 0o755); } catch (e) { /* Windows */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log(` ${green}✓${reset} Installed hooks`);
|
||||
}
|
||||
|
||||
// Add Codex hooks (SessionStart for update checking) — requires codex_hooks feature flag
|
||||
const configPath = path.join(targetDir, 'config.toml');
|
||||
try {
|
||||
|
||||
@@ -830,6 +830,22 @@ describe('Codex install hook configuration (e2e)', () => {
|
||||
fs.rmSync(tmpDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
test('Codex install copies hook file that is referenced in config.toml (#2153)', () => {
|
||||
// Regression test: Codex install writes gsd-check-update hook reference into
|
||||
// config.toml but must also copy the hook file to ~/$CODEX_HOME/hooks/
|
||||
runCodexInstall(codexHome);
|
||||
|
||||
const configContent = readCodexConfig(codexHome);
|
||||
// config.toml must reference the hook
|
||||
assert.ok(configContent.includes('gsd-check-update.js'), 'config.toml references gsd-check-update.js');
|
||||
// The hook file must physically exist at the referenced path
|
||||
const hookFile = path.join(codexHome, 'hooks', 'gsd-check-update.js');
|
||||
assert.ok(
|
||||
fs.existsSync(hookFile),
|
||||
`gsd-check-update.js must exist at ${hookFile} — config.toml references it but file was not installed`
|
||||
);
|
||||
});
|
||||
|
||||
test('fresh CODEX_HOME enables codex_hooks without draft root defaults', () => {
|
||||
runCodexInstall(codexHome);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user