Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 25868ea

Browse files
mksgluclaude
andcommitted
fix(hooks): self-heal updates ALL hook types, not just PreToolUse (mksglu#187)
Extend the pretooluse.mjs self-heal to iterate all hook types in settings.json (SessionStart, PostToolUse, PreCompact, UserPromptSubmit) instead of only fixing PreToolUse paths. After marketplace auto-update, all stale context-mode hook paths are now corrected on first tool call. Previously, SessionStart would fail with "hook error" because its path still pointed to the deleted old version directory, and the self-heal only fixed PreToolUse paths. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
1 parent 1c55925 commit 25868ea

2 files changed

Lines changed: 52 additions & 11 deletions

File tree

hooks/pretooluse.mjs

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -87,28 +87,40 @@ try {
8787
writeFileSync(ipPath, JSON.stringify(ip, null, 2) + "\n", "utf-8");
8888
}
8989

90-
// 3. Update hook path + matcher in settings.json
90+
// 3. Update hook paths + matcher in settings.json for ALL hook types (#187)
91+
// Previously only fixed PreToolUse — SessionStart, PostToolUse, PreCompact,
92+
// UserPromptSubmit paths remained stale after marketplace auto-update.
9193
const settingsPath = resolve(homedir(), ".claude", "settings.json");
9294
try {
9395
const settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
94-
const hooks = settings.hooks?.PreToolUse;
95-
if (Array.isArray(hooks)) {
96-
let changed = false;
97-
for (const entry of hooks) {
98-
// Fix deprecated Task-only matcher → Agent|Task
99-
if (entry.matcher && entry.matcher.includes("Task") && !entry.matcher.includes("Agent")) {
96+
const allHooks = settings.hooks || {};
97+
let changed = false;
98+
99+
for (const hookType of Object.keys(allHooks)) {
100+
const entries = allHooks[hookType];
101+
if (!Array.isArray(entries)) continue;
102+
103+
for (const entry of entries) {
104+
// Fix deprecated Task-only matcher (PreToolUse only)
105+
if (hookType === "PreToolUse" && entry.matcher?.includes("Task") && !entry.matcher.includes("Agent")) {
100106
entry.matcher = entry.matcher.replace("Task", "Agent|Task");
101107
changed = true;
102108
}
109+
// Rewrite stale context-mode hook paths to point to current version
103110
for (const h of (entry.hooks || [])) {
104-
if (h.command?.includes("pretooluse.mjs") && !h.command.includes(targetDir)) {
105-
h.command = "node " + resolve(targetDir, "hooks", "pretooluse.mjs");
106-
changed = true;
111+
if (h.command && h.command.includes(".mjs") && h.command.includes("context-mode") && !h.command.includes(targetDir)) {
112+
// Extract the script filename (e.g., sessionstart.mjs, pretooluse.mjs)
113+
const scriptMatch = h.command.match(/([a-z]+\.mjs)\s*"?\s*$/);
114+
if (scriptMatch) {
115+
h.command = "node " + resolve(targetDir, "hooks", scriptMatch[1]);
116+
changed = true;
117+
}
107118
}
108119
}
109120
}
110-
if (changed) writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
111121
}
122+
123+
if (changed) writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
112124
} catch { /* skip settings update */ }
113125

114126
// Old version dirs are cleaned lazily by sessionstart.mjs (age-gated >1h)

tests/core/cli.test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -790,3 +790,32 @@ describe("Hidden temp dirs (#186)", () => {
790790
expect(EXEC_SOURCE).not.toMatch(/tmpdir\(\),\s*"ctx-mode-"/);
791791
});
792792
});
793+
794+
// ── Issue #187 follow-up: self-heal must fix ALL hook types, not just PreToolUse ──
795+
796+
describe("Self-heal covers all hook types (#187)", () => {
797+
const PRETOOLUSE_SOURCE = readFileSync(resolve(ROOT, "hooks/pretooluse.mjs"), "utf-8");
798+
799+
test("pretooluse.mjs self-heal iterates all hook types in settings.json", () => {
800+
// Must NOT be scoped to only PreToolUse
801+
// Old pattern: settings.hooks?.PreToolUse (only one type)
802+
// New pattern: iterates Object.keys(settings.hooks) or similar
803+
const selfHealSection = PRETOOLUSE_SOURCE.slice(
804+
PRETOOLUSE_SOURCE.indexOf("Update hook path"),
805+
PRETOOLUSE_SOURCE.indexOf("lazy cleanup"),
806+
);
807+
// Must iterate all hook types, not just PreToolUse
808+
expect(selfHealSection).not.toContain("hooks?.PreToolUse");
809+
expect(selfHealSection).toMatch(/Object\.keys|for\s*\(\s*const\s+\w+\s+(of|in)\s+.*hooks/);
810+
});
811+
812+
test("pretooluse.mjs self-heal fixes all context-mode hook scripts", () => {
813+
const selfHealSection = PRETOOLUSE_SOURCE.slice(
814+
PRETOOLUSE_SOURCE.indexOf("Update hook path"),
815+
PRETOOLUSE_SOURCE.indexOf("lazy cleanup"),
816+
);
817+
// Must match any .mjs hook script, not just pretooluse.mjs
818+
expect(selfHealSection).toMatch(/\.mjs/);
819+
expect(selfHealSection).toContain("context-mode");
820+
});
821+
});

0 commit comments

Comments
 (0)