Summary
On the Claude Code desktop app (macOS, Claude Code 2.1.241), the always-on SessionStart hook silently does nothing. No banner, no error, exit 0. ADHD mode never loads and there is no signal that anything went wrong.
The shipped hook command is:
"command": "node -e \"(async()=>{const root=process.env.CLAUDE_PLUGIN_ROOT||process.env.PLUGIN_ROOT;if(root)await import(...)})().catch(()=>{})\""
The desktop app substitutes ${CLAUDE_PLUGIN_ROOT} into hook args, but does not export CLAUDE_PLUGIN_ROOT into the hook process environment. So root is undefined, the if (root) guard skips the import, and the process exits 0 having done nothing.
This appears to be a regression introduced by the fix for #70: moving from sh "${CLAUDE_PLUGIN_ROOT}/hooks/always-on.sh" (template substitution) to reading the env var at runtime. #70 at least failed loudly; this fails silently.
Evidence
Same machine, same install, same ~/.claude/.i-have-adhd-always flag present, plugin enabled=true.
Terminal claude — works:
[DEBUG] Loading hooks from plugin: i-have-adhd
[DEBUG] Hook SessionStart:startup (SessionStart) success:
ADHD MODE ACTIVE (always-on). ...
Desktop app, same session start type (SessionStart:clear) — no banner. Another plugin's SessionStart hook in the same session fired normally, so hooks themselves are working; that plugin uses "args": ["${CLAUDE_PLUGIN_ROOT}/hooks/activate.js"] (substitution) rather than reading the env var.
Reproducing the no-op directly:
$ env -u CLAUDE_PLUGIN_ROOT -u PLUGIN_ROOT node -e "(async()=>{const root=process.env.CLAUDE_PLUGIN_ROOT||process.env.PLUGIN_ROOT;if(root)await import(...)})().catch(()=>{})"
$ echo $?
0
No output, no error.
Why this is worse than a crash
The if (root) guard and the .catch(()=>{}) together make every failure invisible. A user with always-on enabled has no way to tell that the ruleset isn't loading — the agent just quietly behaves normally. I lost a while assuming the plugin wasn't installed.
Suggested fix
The repo already ships hooks/always-on.sh, which resolves its own path from $0 and needs no env var. The Node path just never falls back to it. Two options:
- Fall back when
root is missing. Resolve relative to the hook script's own location — e.g. derive from import.meta.url / __filename instead of trusting the env var, which removes the dependency entirely.
- Chain in
hooks.json: keep the Node hook, add the POSIX hook as a second SessionStart entry guarded so only one produces output.
Option 1 seems cleaner and would also have prevented #70.
At minimum, consider making the failure visible — a one-line stderr note when root is absent would have made this a two-minute diagnosis instead of a hunt.
Environment
- macOS 15 (Darwin 25.6.0)
- Claude Code 2.1.241, desktop app
- i-have-adhd 0.2.0, installed via marketplace,
enabled=true
CLAUDE_CONFIG_DIR unset, flag at ~/.claude/.i-have-adhd-always present
Summary
On the Claude Code desktop app (macOS, Claude Code 2.1.241), the always-on
SessionStarthook silently does nothing. No banner, no error, exit 0. ADHD mode never loads and there is no signal that anything went wrong.The shipped hook command is:
The desktop app substitutes
${CLAUDE_PLUGIN_ROOT}into hookargs, but does not exportCLAUDE_PLUGIN_ROOTinto the hook process environment. Sorootisundefined, theif (root)guard skips the import, and the process exits 0 having done nothing.This appears to be a regression introduced by the fix for #70: moving from
sh "${CLAUDE_PLUGIN_ROOT}/hooks/always-on.sh"(template substitution) to reading the env var at runtime. #70 at least failed loudly; this fails silently.Evidence
Same machine, same install, same
~/.claude/.i-have-adhd-alwaysflag present, pluginenabled=true.Terminal
claude— works:Desktop app, same session start type (
SessionStart:clear) — no banner. Another plugin's SessionStart hook in the same session fired normally, so hooks themselves are working; that plugin uses"args": ["${CLAUDE_PLUGIN_ROOT}/hooks/activate.js"](substitution) rather than reading the env var.Reproducing the no-op directly:
No output, no error.
Why this is worse than a crash
The
if (root)guard and the.catch(()=>{})together make every failure invisible. A user with always-on enabled has no way to tell that the ruleset isn't loading — the agent just quietly behaves normally. I lost a while assuming the plugin wasn't installed.Suggested fix
The repo already ships
hooks/always-on.sh, which resolves its own path from$0and needs no env var. The Node path just never falls back to it. Two options:rootis missing. Resolve relative to the hook script's own location — e.g. derive fromimport.meta.url/__filenameinstead of trusting the env var, which removes the dependency entirely.hooks.json: keep the Node hook, add the POSIX hook as a secondSessionStartentry guarded so only one produces output.Option 1 seems cleaner and would also have prevented #70.
At minimum, consider making the failure visible — a one-line stderr note when
rootis absent would have made this a two-minute diagnosis instead of a hunt.Environment
enabled=trueCLAUDE_CONFIG_DIRunset, flag at~/.claude/.i-have-adhd-alwayspresent