Skip to content

Fix Windows always-on SessionStart hook: launch via Node instead of sh - #71

Open
Souptik96 wants to merge 1 commit into
ayghri:mainfrom
Souptik96:fix/windows-always-on-hook
Open

Fix Windows always-on SessionStart hook: launch via Node instead of sh#71
Souptik96 wants to merge 1 commit into
ayghri:mainfrom
Souptik96:fix/windows-always-on-hook

Conversation

@Souptik96

Copy link
Copy Markdown

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 sh is not on PATH — so every
session start reported a hook error.

I instrumented the hook and verified how Claude Code actually handles the command on Windows:

ARGV=["…node.exe","C:/Users/…/i-have-adhd/"]

That is, Claude Code expands ${CLAUDE_PLUGIN_ROOT} itself (to a real, forward-slash
path — 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 breaks
it on current Claude Code — the only blocker is the missing sh. (#66 had moved the hook
from Node to sh to drop the Node dependency, which is what introduced the Windows breakage.)

Fix

  • Add hooks/always-on.mjs — a Node port of always-on.sh with identical behavior: opt-in
    flag check ($CLAUDE_CONFIG_DIR/.i-have-adhd-always), resolve SKILL.md relative to the
    script (import.meta.url, not a trusted env var), strip YAML frontmatter, print the banner
    • body, swallow all errors and exit 0 (never blocks session start).
  • Point hooks/hooks.json at node "${CLAUDE_PLUGIN_ROOT}/hooks/always-on.mjs". Node ships
    with 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).
  • Keep always-on.sh unchanged as the POSIX fallback.

A combined node … || sh … command was intentionally avoided: Windows PowerShell 5.1 doesn't
support || (would reintroduce a parse error) and Windows has no sh regardless.

Testing (Windows 11, PowerShell, Claude Code 2.1.218, Node 20)

  • node hooks/always-on.mjs with the flag absent → no output, exit 0 (silent no-op preserved).
  • node hooks/always-on.mjs with the flag present → prints ADHD MODE ACTIVE (always-on)…
    followed by the frontmatter-stripped SKILL.md body, exit 0.
  • End-to-end: installed the plugin from a local marketplace, created the flag, ran a Claude
    Code session → the SessionStart hook injected the ruleset (model confirmed receipt) with
    no startup hook error.
  • Not verified locally: the POSIX sh fallback (no sh/Git-Bash on the test box). always-on.sh
    is unchanged.

…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.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 SessionStart hook command to node "${CLAUDE_PLUGIN_ROOT}/hooks/always-on.mjs".
  • Add hooks/always-on.mjs, a Node implementation of the existing always-on behavior (flag check, load SKILL.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 thread hooks/always-on.mjs
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/, "");
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SessionStart always-on hook errors on Windows: 'sh' and ${CLAUDE_PLUGIN_ROOT} don't resolve under PowerShell

2 participants