From 8bbb3eaabc0e504a357e97b7475ef42a4bbcb14c Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Fri, 14 Aug 2026 10:55:12 -0400 Subject: [PATCH] fix(#3329): reconcile stale managed .sh hook commands on install/update (#3460) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(#3329): reconcile stale managed .sh hook commands on install/update applySettingsJsonHooks registers the four .sh managed hooks only-if-absent, so entries registered before the #580/#3393 shellHookOmitsBashRunner fix kept their bash-runner-prefixed commands forever — /gsd-update re-invokes the installer but never re-derived existing entries. Add reconcileManagedShellHookCommands (wired into applySettingsJsonHooks): on win32+claude it rewrites existing managed .sh entries to the command this install would generate today, scoped to exact managed basenames so user-authored hooks are untouched, and inert wherever the bash runner is still the correct shape. Also bumps the allow-test-rule ceiling 301→302: PR #3455 added tests/milestone-lock.test.cjs (the 302nd marked file) without the ratchet bump, leaving lint-tests red on next. * chore(#3329): add changeset fragment * chore(#3329): backfill changeset pr number 3460 --------- Co-authored-by: sim --- .changeset/quick-rams-greet.md | 5 + bin/install.js | 10 ++ src/runtime-hooks-surface.cts | 107 ++++++++++++++++ tests/install-regressions.test.cjs | 190 +++++++++++++++++++++++++++++ 4 files changed, 312 insertions(+) create mode 100644 .changeset/quick-rams-greet.md diff --git a/.changeset/quick-rams-greet.md b/.changeset/quick-rams-greet.md new file mode 100644 index 000000000..5bad0882c --- /dev/null +++ b/.changeset/quick-rams-greet.md @@ -0,0 +1,5 @@ +--- +type: Fixed +pr: 3460 +--- +Windows/Claude Code: /gsd-update and re-running the installer now migrate stale `.sh` managed hook commands (gsd-validate-commit, gsd-graphify-update, gsd-session-state, gsd-phase-boundary) in settings.json/settings.local.json to the current bash-runner-omission format — removing the redundant nested bash that the pre-#580/#3393 shape spawns on every hook fire. Custom user hooks are never touched. diff --git a/bin/install.js b/bin/install.js index bd5e98a95..3008f1ed2 100755 --- a/bin/install.js +++ b/bin/install.js @@ -1106,6 +1106,15 @@ function rewriteLegacyManagedNodeHookCommands(settings, absoluteRunner, opts) { return hooksSurface.rewriteLegacyManagedNodeHookCommands(settings, absoluteRunner, opts); } +// #3329: rewrite already-registered managed `.sh` hook commands to the shape +// the current installer would generate (the #580/#3393 bash-runner-omission +// migration the register-only-if-absent path never applied to existing +// entries). Invoked inside applySettingsJsonHooks in the compiled surface +// module; re-bound here for tests/consumers, mirroring the #2979 rewriter. +function reconcileManagedShellHookCommands(settings, expected, opts) { + return hooksSurface.reconcileManagedShellHookCommands(settings, expected, opts); +} + /** * Build the GSD-managed Codex SessionStart hook block for config.toml. * @@ -13784,6 +13793,7 @@ module.exports = { referencesHook, applySettingsJsonHooks, rewriteLegacyManagedNodeHookCommands, + reconcileManagedShellHookCommands, buildCodexHookBlock, rewriteLegacyCodexHookBlock, buildCodexHookWindowsShimIR, diff --git a/src/runtime-hooks-surface.cts b/src/runtime-hooks-surface.cts index 3f1d9f7b5..f398893d8 100644 --- a/src/runtime-hooks-surface.cts +++ b/src/runtime-hooks-surface.cts @@ -568,6 +568,95 @@ function rewriteLegacyManagedNodeHookCommands(settings: Settings, absoluteRunner return changed; } +// --------------------------------------------------------------------------- +// Shared: reconcileManagedShellHookCommands (#3329) +// --------------------------------------------------------------------------- + +/** + * Rewrite already-registered managed `.sh` hook `command` strings to the shape + * the current installer would generate (#3329). + * + * applySettingsJsonHooks registers the four `.sh` managed hooks only-if-absent, + * so an entry registered by an older installer keeps its old command forever — + * `/gsd-update` (which re-invokes the installer) never re-derived it. On + * Claude/win32 that left the pre-#580/#3393 bash-runner-prefixed commands + * (`bash "