fix(hooks): use shared cache dir and correct stale hooks path

Two fixes for multi-runtime installations:

1. Update cache now writes to ~/.cache/gsd/ instead of the runtime-
   specific config dir, preventing mismatches when check-update and
   statusline resolve to different runtimes. Statusline reads from
   shared path first with legacy fallback.

2. Stale hooks detection now checks configDir/hooks/ where hooks are
   actually installed, not configDir/get-shit-done/hooks/ which does
   not exist.

Closes #1421

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Tibsfox
2026-03-27 11:26:06 -07:00
parent 1421dc07bc
commit 9ef71b0b70
3 changed files with 16 additions and 13 deletions

View File

@@ -29,7 +29,10 @@ function detectConfigDir(baseDir) {
const globalConfigDir = detectConfigDir(homeDir);
const projectConfigDir = detectConfigDir(cwd);
const cacheDir = path.join(globalConfigDir, 'cache');
// Use a shared, tool-agnostic cache directory to avoid multi-runtime
// resolution mismatches where check-update writes to one runtime's cache
// but statusline reads from another (#1421).
const cacheDir = path.join(homeDir, '.cache', 'gsd');
const cacheFile = path.join(cacheDir, 'gsd-update-check.json');
// VERSION file locations (check project first, then global)
@@ -65,10 +68,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/
// Hooks are installed at configDir/hooks/ (e.g. ~/.claude/hooks/) (#1421)
let staleHooks = [];
if (configDir) {
const hooksDir = path.join(configDir, 'get-shit-done', 'hooks');
const hooksDir = path.join(configDir, 'hooks');
try {
if (fs.existsSync(hooksDir)) {
const hookFiles = fs.readdirSync(hooksDir).filter(f => f.startsWith('gsd-') && f.endsWith('.js'));

View File

@@ -92,8 +92,12 @@ process.stdin.on('end', () => {
}
// GSD update available?
// Check shared cache first (#1421), fall back to runtime-specific cache for
// backward compatibility with older gsd-check-update.js versions.
let gsdUpdate = '';
const cacheFile = path.join(claudeDir, 'cache', 'gsd-update-check.json');
const sharedCacheFile = path.join(homeDir, '.cache', 'gsd', 'gsd-update-check.json');
const legacyCacheFile = path.join(claudeDir, 'cache', 'gsd-update-check.json');
const cacheFile = fs.existsSync(sharedCacheFile) ? sharedCacheFile : legacyCacheFile;
if (fs.existsSync(cacheFile)) {
try {
const cache = JSON.parse(fs.readFileSync(cacheFile, 'utf8'));

View File

@@ -996,19 +996,15 @@ 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/', () => {
test('gsd-check-update.js checks configDir/hooks/ where hooks are actually installed (#1421)', () => {
const content = fs.readFileSync(
path.join(__dirname, '..', 'hooks', 'gsd-check-update.js'), 'utf-8'
);
// Hooks are installed at configDir/hooks/ (e.g. ~/.claude/hooks/),
// not configDir/get-shit-done/hooks/ which doesn't exist (#1421)
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'
content.includes("path.join(configDir, 'hooks')"),
'stale hook check must look in configDir/hooks/ where hooks are actually installed'
);
});
});