From c4b313c60f8d84dcb11c30042a7a0a2e768f8506 Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Fri, 20 Mar 2026 16:24:06 -0400 Subject: [PATCH] fix: stale hook detection checks wrong directory path gsd-check-update.js looked for hooks in configDir/hooks/ (e.g., ~/.claude/hooks/) but the installer writes hooks to configDir/get-shit-done/hooks/. This mismatch caused false stale hook warnings that persisted even after updating. Also clears the update cache during install so the next session re-evaluates hook versions with the correct path. Closes #1249 Co-Authored-By: Claude Opus 4.6 --- bin/install.js | 5 +++++ hooks/gsd-check-update.js | 3 ++- tests/core.test.cjs | 20 ++++++++++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/bin/install.js b/bin/install.js index 674120eb4..4f8eeced7 100755 --- a/bin/install.js +++ b/bin/install.js @@ -3900,6 +3900,11 @@ function install(isGlobal, runtime = 'claude') { } } + // Clear stale update cache so next session re-evaluates hook versions + // targetDir is e.g. ~/.claude/get-shit-done/, parent is the config dir + const updateCacheFile = path.join(path.dirname(targetDir), 'cache', 'gsd-update-check.json'); + try { fs.unlinkSync(updateCacheFile); } catch (e) { /* cache may not exist yet */ } + if (failures.length > 0) { console.error(`\n ${yellow}Installation incomplete!${reset} Failed: ${failures.join(', ')}`); process.exit(1); diff --git a/hooks/gsd-check-update.js b/hooks/gsd-check-update.js index 9076ec038..1b7b27ed3 100755 --- a/hooks/gsd-check-update.js +++ b/hooks/gsd-check-update.js @@ -65,9 +65,10 @@ const child = spawn(process.execPath, ['-e', ` } catch (e) {} // Check for stale hooks — compare hook version headers against installed VERSION + // Hooks live inside get-shit-done/hooks/, not configDir/hooks/ let staleHooks = []; if (configDir) { - const hooksDir = path.join(configDir, 'hooks'); + const hooksDir = path.join(configDir, 'get-shit-done', 'hooks'); try { if (fs.existsSync(hooksDir)) { const hookFiles = fs.readdirSync(hooksDir).filter(f => f.startsWith('gsd-') && f.endsWith('.js')); diff --git a/tests/core.test.cjs b/tests/core.test.cjs index e43f53f9f..251793d46 100644 --- a/tests/core.test.cjs +++ b/tests/core.test.cjs @@ -967,6 +967,26 @@ describe('stale hook filter', () => { }); }); +// ─── stale hook path regression (#1249) ────────────────────────────────────── + +describe('stale hook path', () => { + test('gsd-check-update.js checks get-shit-done/hooks/ not configDir/hooks/', () => { + const content = fs.readFileSync( + path.join(__dirname, '..', 'hooks', 'gsd-check-update.js'), 'utf-8' + ); + assert.ok( + content.includes("path.join(configDir, 'get-shit-done', 'hooks')"), + 'stale hook check must look in configDir/get-shit-done/hooks/, not configDir/hooks/' + ); + assert.ok( + !content.includes("path.join(configDir, 'hooks')") || + content.indexOf("path.join(configDir, 'get-shit-done', 'hooks')") < + content.indexOf("path.join(configDir, 'hooks')") + 100, // allow the old pattern only if corrected version exists first + 'should not use the wrong hooks path' + ); + }); +}); + // ─── resolveWorktreeRoot ───────────────────────────────────────────────────── describe('resolveWorktreeRoot', () => {