Skip to content

Commit 4fc50ad

Browse files
authored
fix(cursor-hooks): preserve similarly named user hooks (#1052)
* fix(cursor-hooks): only match ponytail hook paths * test(cursor-hooks): preserve similarly named user hooks * refactor(cursor-hooks): use bounded hook regex * test(cursor-hooks): cover later lookalike argument
1 parent 59e9ac5 commit 4fc50ad

2 files changed

Lines changed: 10 additions & 1 deletion

File tree

‎scripts/cursor-hooks.js‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ const { isShellSafe } = require('../hooks/ponytail-config');
1515

1616
const ROOT = path.join(__dirname, '..');
1717
const TEMPLATE = path.join(ROOT, 'hooks', 'cursor-hooks.json');
18-
const PONYTAIL_HOOK = /ponytail-[\w-]+\.js/;
18+
const PONYTAIL_HOOK = /[\\/]hooks[\\/]ponytail-[\w-]+\.js(?=["'\s]|$)/;
1919

2020
function isPonytailHook(entry) {
2121
return Boolean(entry && typeof entry.command === 'string' && PONYTAIL_HOOK.test(entry.command));

‎tests/cursor-hooks.test.js‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ const { spawnSync } = require('child_process');
1919
const root = path.join(__dirname, '..');
2020
const rootFwd = root.replace(/\\/g, '/');
2121
const TEMPLATE = 'hooks/cursor-hooks.json';
22+
const { isPonytailHook } = require('../scripts/cursor-hooks');
2223

2324
// Keep host detection and config resolution deterministic whatever shell the
2425
// suite runs in (a Cursor hook env, a Codex shell, a machine-wide default mode).
@@ -106,6 +107,12 @@ test('cursor hooks template is a valid hooks.json with the two events that can i
106107
}
107108
});
108109

110+
test('hook detection ignores later lookalike arguments after a real ponytail hook', () => {
111+
assert.equal(isPonytailHook({
112+
command: 'node "/opt/p/hooks/ponytail-activate.js" --log /tmp/hooks/ponytail-x/',
113+
}), true);
114+
});
115+
109116
test('isCursor is off outside a Cursor hook process', () => {
110117
const { isCursor } = require('../hooks/ponytail-runtime');
111118
assert.equal(isCursor, false);
@@ -231,6 +238,7 @@ test('installer merges into an existing ~/.cursor/hooks.json and leaves unrelate
231238
hooks: {
232239
sessionStart: [{ command: './hooks/their-session.sh' }],
233240
afterFileEdit: [{ command: './hooks/format.sh', matcher: 'Write' }],
241+
stop: [{ command: 'node "./hooks/my-ponytail-logger.js"' }],
234242
},
235243
};
236244
fs.writeFileSync(file, JSON.stringify(theirs));
@@ -241,6 +249,7 @@ test('installer merges into an existing ~/.cursor/hooks.json and leaves unrelate
241249
let config = JSON.parse(fs.readFileSync(file, 'utf8'));
242250
assert.equal(config.version, 1);
243251
assert.deepEqual(config.hooks.afterFileEdit, theirs.hooks.afterFileEdit);
252+
assert.deepEqual(config.hooks.stop, theirs.hooks.stop, 'similarly named user hook must survive install');
244253
assert.equal(config.hooks.sessionStart[0].command, './hooks/their-session.sh', 'their sessionStart hook stays first and intact');
245254
assert.equal(config.hooks.sessionStart.length, 2);
246255
assert.deepEqual(config.hooks.sessionStart[1], { command: `node "${rootFwd}/hooks/ponytail-activate.js"`, timeout: 5 });

0 commit comments

Comments
 (0)