How Claude Code Hooks Enable Always-On Behavior for i-have-adhd
Claude Code hooks enable always-on behavior for i-have-adhd through a SessionStart hook that automatically loads the ADHD skill rules at the start of every session when the user has opted in.
The i-have-adhd plugin for Claude Code leverages the hooks system to deliver a seamless, automatic experience for users who need ADHD-friendly formatting. By registering a session lifecycle hook and implementing a lightweight opt-in flag, the skill activates itself without requiring manual commands each time. This article breaks down exactly how the hook infrastructure works, using the actual implementation from the ayghri/i-have-adhd repository.
Understanding the SessionStart Hook Mechanism
The foundation of always-on behavior is defined in hooks/hooks.json. This file registers a SessionStart hook that matches four session states: startup, resume, clear, and compact. When any of these events occur, Claude Code executes a Node.js command that dynamically imports the always-on module.
{
"SessionStart": [
{
"matcher": "startup|resume|clear|compact",
"hooks": [
{
"type": "command",
"command": "node -e \"(async()=>{const root=process.env.CLAUDE_PLUGIN_ROOT||process.env.PLUGIN_ROOT;if(root)await import(require('node:url').pathToFileURL(require('node:path').join(root,'hooks','always-on.mjs')).href)})().catch(()=>{})\"",
"timeout": 30,
"statusMessage": "Checking i-have-adhd always-on flag..."
}
]
}
]
}
The command uses dynamic import() with pathToFileURL to ensure cross-platform compatibility. The .catch(()=>{}) wrapper guarantees that any import failure exits silently rather than crashing the session.
How the Always-On Module Works
The hooks/always-on.mjs file implements three critical behaviors that make always-on mode reliable and unobtrusive.
Opt-In Flag Check
The module first checks for a hidden flag file at $CLAUDE_CONFIG_DIR/.i-have-adhd-always, defaulting to ~/.claude/.i-have-adhd-always. If the file does not exist, the script immediately exits with status 0, leaving the session completely unchanged.
const claudeDir = process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), ".claude");
const flagPath = path.join(claudeDir, ".i-have-adhd-always");
if (!fs.existsSync(flagPath)) process.exit(0);
Skill Loading and Output
When the flag exists, the module resolves skills/i-have-adhd/SKILL.md relative to its location, reads the content, strips YAML front-matter, and writes the rules directly to standard output. This streams the full rule set into Claude's context before any user message is processed.
process.stdout.write(
"ADHD MODE ACTIVE (always-on). The ruleset below applies to every response…\n\n" + body
);
Non-Blocking Error Handling
Every operation is wrapped in error handling that exits with status 0. Missing files, permission errors, or read failures never block session startup. The user gets either the ADHD rules or plain behavior—never a broken session.
Enabling and Disabling Always-On Mode
Users control the always-on behavior through simple file operations.
Enable always-on permanently:
mkdir -p ~/.claude
touch ~/.claude/.i-have-adhd-always
Disable for the current session only:
/i-have-adhd stop adhd mode
Disable permanently:
rm ~/.claude/.i-have-adhd-always
Testing the Hook Manually
To verify the hook executes correctly without starting a full Claude session, run this from the plugin root directory:
node -e "const root=process.env.CLAUDE_PLUGIN_ROOT||process.env.PLUGIN_ROOT;
if (root) await import(require('node:url').pathToFileURL(
require('node:path').join(root,'hooks','always-on.mjs')).href)"
The script outputs nothing if the flag file is absent, or streams the ADHD rules if present.
Key Files in the Always-On Architecture
| File | Purpose |
|---|---|
hooks/hooks.json |
Registers the SessionStart hook with matcher pattern and command configuration |
hooks/always-on.mjs |
Implements opt-in detection, skill file loading, and rule streaming |
skills/i-have-adhd/SKILL.md |
Contains the canonical ADHD-friendly formatting rules injected by the hook |
Summary
- SessionStart hooks in
hooks/hooks.jsontrigger onstartup|resume|clear|compactevents - Dynamic Node execution imports
always-on.mjswithout blocking session initialization - Opt-in flag file at
~/.claude/.i-have-adhd-alwaysgives users full control over activation - Direct stdout streaming injects
SKILL.mdrules into Claude's context before first response - Zero-fail design ensures session startup never breaks regardless of file system state
Frequently Asked Questions
What happens if the flag file is missing?
The always-on.mjs script exits immediately with status 0. Claude Code starts normally without ADHD formatting applied. No error messages appear, and the session proceeds unchanged.
Why use a file-based flag instead of environment variables?
File-based flags persist across terminal sessions and system restarts without requiring shell profile modifications. Users can toggle the behavior from any terminal or file manager, and the state survives Claude Code updates or reinstalls.
Can the always-on hook slow down session startup?
The hook has a 30-second timeout configured in hooks.json, but actual execution typically completes in milliseconds. The script performs only synchronous file existence checks and a single file read operation before exiting.
How does this differ from manually loading the skill?
Manual loading requires typing a command each session. The hook approach guarantees consistency—users never forget to activate the mode, and the rules apply from the very first Claude response without conscious effort.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →