fix(#3662): resolve managed hook node runners at hook-fire time (#3790)

* test(#3662): failing-first suite for runtime-resolving hook runners

* fix(#3662): resolve managed hook node runners at hook-fire time

* fix(#3662): close review findings and document the resolver

* fix(#3662): close adversarial and security review findings

* chore(#3662): backfill changeset pr number

* test(#3662): honor win32 skip return and platform-aware sh runner pin

* test(#3662): pin the bare win32-claude sh-hook shape omitting the bash runner

---------

Co-authored-by: sim <sim@local>
This commit is contained in:
Tom Boucher
2026-08-24 00:07:06 -04:00
committed by GitHub
parent cf15682d1c
commit 4af59f8dd3
25 changed files with 896 additions and 46 deletions

View File

@@ -550,6 +550,7 @@
"gsd-cursor-subagent-stop.js",
"gsd-ensure-canonical-path.js",
"gsd-graphify-update.sh",
"gsd-node-runner.sh",
"gsd-phase-boundary.sh",
"gsd-prompt-guard.js",
"gsd-read-guard.js",

View File

@@ -706,6 +706,7 @@ Full listing: `hooks/`.
| `gsd-validate-commit.sh` | `PostToolUse` | Commit validation for conventional-commit enforcement |
| `gsd-phase-boundary.sh` | `PostToolUse` | Phase-boundary detection for workflow transitions |
| `gsd-graphify-update.sh` | `PostToolUse` | Auto-rebuild knowledge graph after main HEAD advances (opt-in, default off — #3347) |
| `gsd-node-runner.sh` | (helper) | Portable node resolver managed JS hook commands route through under `--portable-hooks`: install-time node path first, then `command -v node`, then well-known layouts — resolves at hook-fire time so a shared config root works in every environment (#3662) |
---

View File

@@ -525,6 +525,33 @@ Select the corresponding stable runtime in the installer prompt. GSD does not en
---
## Sharing one config root across environments
When one config root (`~/.claude`, `~/.config/opencode`, …) is mounted or synced into
machines with different Node.js layouts — a host plus Docker containers bind-mounting the
same directory, or WSL and Windows sharing a drive — install with `--portable-hooks`
(or `GSD_PORTABLE_HOOKS=1`):
```bash
npx @opengsd/gsd-core@latest --claude --global --portable-hooks
```
Hook script paths are emitted `$HOME`-relative, and every managed JavaScript hook command
resolves its node binary at hook-fire time instead of depending on whichever machine ran
the installer (#3662): portable installs route through a staged
`hooks/gsd-node-runner.sh` resolver (the install-time node path first, then `command -v
node`, then the well-known layouts); non-portable installs carry an equivalent inline
fallback chain. The install-time path is always tried first, so GUI launches with a
minimal `PATH` keep working, and a bare `node` lookup is never depended on. Re-running
install or update from any of the environments converges stale runner paths — no mixed
state where some hooks work in one environment and the rest in another.
To pin down which node a given environment picks, run the resolver directly with
`GSD_NODE_RUNNER_NO_FALLBACKS=1` (first-argument-only resolution) — it prints a stderr
diagnostic and exits non-zero when nothing resolves.
---
## Installing without Node.js
If you cannot run `npx` (for example, on a Windows machine without Node.js), you have two options.