Fix Windows always-on SessionStart hook: launch via Node instead of sh - #71
Open
Souptik96 wants to merge 1 commit into
Open
Fix Windows always-on SessionStart hook: launch via Node instead of sh#71Souptik96 wants to merge 1 commit into
Souptik96 wants to merge 1 commit into
Conversation
…bility
The SessionStart hook ran `sh "${CLAUDE_PLUGIN_ROOT}/hooks/always-on.sh"`,
which fails on Windows: Claude Code executes hook commands through PowerShell,
where `sh` is not on PATH, so every session start reported a hook error (ayghri#70).
(ayghri#66 had moved the hook from Node to sh to drop the Node dependency, trading a
missing-node failure for this missing-sh failure.)
Restore a Node implementation and launch it directly:
- Add hooks/always-on.mjs: same behavior as always-on.sh (opt-in flag check,
resolve SKILL.md relative to the script via import.meta.url, strip YAML
frontmatter, print the banner + body, swallow all errors and exit 0).
- Point hooks.json at `node "${CLAUDE_PLUGIN_ROOT}/hooks/always-on.mjs"`. Node
ships with Claude Code on every platform and runs identically, so this works
under PowerShell/cmd (Windows) and sh (macOS/Linux). Verified on Windows:
Claude Code expands ${CLAUDE_PLUGIN_ROOT} to a real forward-slash path before
invoking the command and `node <path>` runs, so the empty-${VAR} theory was
not the actual cause -- the missing `sh` was.
- always-on.sh is kept unchanged as the POSIX fallback.
A combined `node ... || sh ...` command was intentionally avoided: Windows
PowerShell 5.1 does not support `||` (it would reintroduce a parse error), and
Windows has no `sh` regardless.
There was a problem hiding this comment.
Pull request overview
Next action: update the frontmatter-stripping regex in hooks/always-on.mjs to match the existing POSIX behavior, then re-run the Windows hook smoke test (node hooks/always-on.mjs with flag present/absent).
This PR fixes the Windows SessionStart hook failure by switching the always-on hook launcher from a POSIX sh command (not available on Windows PowerShell) to a Node-based .mjs hook script that runs cross-platform in Claude Code.
Changes:
- Switch
SessionStarthook command tonode "${CLAUDE_PLUGIN_ROOT}/hooks/always-on.mjs". - Add
hooks/always-on.mjs, a Node implementation of the existing always-on behavior (flag check, loadSKILL.md, strip YAML frontmatter, print banner + body, never fail the session).
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| hooks/hooks.json | Points the SessionStart always-on hook at Node to avoid sh on Windows. |
| hooks/always-on.mjs | Implements the always-on injector in Node (opt-in flag, read/strip SKILL.md, output banner + rules, exit 0 on errors). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+27
to
+30
| // Strip a leading YAML frontmatter block (--- ... --- at the very top of file). | ||
| const body = fs | ||
| .readFileSync(skillPath, "utf8") | ||
| .replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n/, ""); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #70
Root cause (confirmed on Windows + Claude Code 2.1.218)
The SessionStart hook ran
sh "${CLAUDE_PLUGIN_ROOT}/hooks/always-on.sh". On Windows,Claude Code runs hook commands through PowerShell, where
shis not on PATH — so everysession start reported a hook error.
I instrumented the hook and verified how Claude Code actually handles the command on Windows:
That is, Claude Code expands
${CLAUDE_PLUGIN_ROOT}itself (to a real, forward-slashpath — not empty) before the command reaches the shell, and
node <path>executes fine.So the "
${CLAUDE_PLUGIN_ROOT}resolves to empty under PowerShell" theory isn't what breaksit on current Claude Code — the only blocker is the missing
sh. (#66 had moved the hookfrom Node to
shto drop the Node dependency, which is what introduced the Windows breakage.)Fix
hooks/always-on.mjs— a Node port ofalways-on.shwith identical behavior: opt-inflag check (
$CLAUDE_CONFIG_DIR/.i-have-adhd-always), resolveSKILL.mdrelative to thescript (
import.meta.url, not a trusted env var), strip YAML frontmatter, print the bannerhooks/hooks.jsonatnode "${CLAUDE_PLUGIN_ROOT}/hooks/always-on.mjs". Node shipswith Claude Code on all platforms and runs identically, so this works under PowerShell/cmd
(Windows) and sh (macOS/Linux). No reliance on a POSIX shell or on the shell interpolating
${VAR}(Claude Code does the substitution).always-on.shunchanged as the POSIX fallback.A combined
node … || sh …command was intentionally avoided: Windows PowerShell 5.1 doesn'tsupport
||(would reintroduce a parse error) and Windows has noshregardless.Testing (Windows 11, PowerShell, Claude Code 2.1.218, Node 20)
node hooks/always-on.mjswith the flag absent → no output, exit 0 (silent no-op preserved).node hooks/always-on.mjswith the flag present → printsADHD MODE ACTIVE (always-on)…followed by the frontmatter-stripped
SKILL.mdbody, exit 0.Code session → the SessionStart hook injected the ruleset (model confirmed receipt) with
no startup hook error.
shfallback (nosh/Git-Bash on the test box).always-on.shis unchanged.