How the Always-On Hook Mechanism Works in the i-have-adhd Claude Plugin (Plus Why It Fails)
The always-on hook in the i-have-adhd plugin is a SessionStart hook that injects ADHD formatting rules into every Claude Code response, but it fails silently when users haven't created the required opt-in flag file or when environment variables point to the wrong config directory.
The i-have-adhd plugin by Ayoub Ghribi provides ADHD-friendly AI responses through Claude Code's plugin system. Its always-on hook mechanism ensures these formatting rules apply automatically to every conversation—when properly configured. Understanding how this hook works and why it can silently fail helps users troubleshoot missing functionality.
How the Always-On Hook Executes
Claude Code triggers the hook at session start through the SessionStart event defined in [hooks/hooks.json](https://github.com/ayghri/i-have-adhd/blob/main/hooks/hooks.json):
{
"type": "command",
"command": "sh \"${CLAUDE_PLUGIN_ROOT}/hooks/always-on.sh\"",
"timeout": 5,
"statusMessage": "Checking i-have-adhd always-on flag..."
}
The hook delegates to [hooks/always-on.sh](https://github.com/ayghri/i-have-adhd/blob/main/hooks/always-on.sh), a POSIX shell script that performs five sequential operations.
Step 1: Resolve the Config Directory
claude_dir="${CLAUDE_CONFIG_DIR:-$HOME/.claude}"
The script first determines where to look for user configuration. It respects the $CLAUDE_CONFIG_DIR environment variable, falling back to ~/.claude when unset. This allows users to relocate their Claude configuration while maintaining compatibility with standard setups.
Step 2: Check the Opt-In Flag
flag_path="$claude_dir/.i-have-adhd-always"
[ -f "$flag_path" ] || exit 0
The always-on flag file (~/.claude/.i-have-adhd-always) acts as a user consent mechanism. The plugin only activates when this file exists. Critically, the script exits with status 0 when absent—this silent success prevents session start failures for users who haven't opted in.
Step 3: Locate the Skill Ruleset
script_dir=$(dirname -- "$0")
skill_path="$script_dir/../skills/i-have-adhd/SKILL.md"
[ -f "$skill_path" ] || exit 0
The script computes the path to [skills/i-have-adhd/SKILL.md](https://github.com/ayghri/i-have-adhd/blob/main/skills/i-have-adhd/SKILL.md), which contains the full ADHD formatting guidelines. Another exit 0 guards against repository layout changes or missing files.
Step 4: Strip YAML Front-Matter
body=$(awk '
NR == 1 && $0 ~ /^---[[:space:]]*$/ { in_fm = 1; next }
in_fm && $0 ~ /^---[[:space:]]*$/ { in_fm = 0; next }
!in_fm { print }
' "$skill_path")
An AWK block processes SKILL.md to remove YAML front-matter (content between --- delimiters). Only the actual rule content—not metadata like title or description—is injected into conversations.
Step 5: Output the Active Ruleset
printf 'ADHD MODE ACTIVE (always‑on). The ruleset below applies to every response for this session.\n\n'
printf '%s...\n' "$skill_path"
printf '\n%s\n' "$body"
The script prints a confirmation header, the full skill path, and the extracted rules. This output becomes part of Claude's system context for the session.
Why the Always-On Hook Appears to Fail
The hook's fail-safe design (always exiting 0) creates silent failures. Users see no error messages—just missing ADHD formatting. Here are the seven primary failure scenarios:
Missing Flag File (Most Common)
- Symptom: No ADHD rules appear; session starts normally
- Cause: User never created
~/.claude/.i-have-adhd-always - Detection: Run
ls -la ~/.claude/.i-have-adhd-alwaysto verify existence
Wrong Config Directory
- Symptom: Flag file exists elsewhere but rules don't inject
- Cause:
$CLAUDE_CONFIG_DIRoverrides the default path; script still searches computed$claude_dir - Fix: Ensure flag file exists at
$CLAUDE_CONFIG_DIR/.i-have-adhd-always
Permission Problems
- Symptom: Silent exit despite flag file presence
- Cause:
always-on.shlacks execute permission, or flag file isn't readable - Check:
ls -l hooks/always-on.shand verifyr-xpermissions
Missing or Relocated SKILL.md
- Symptom: "ADHD MODE ACTIVE" header appears, but no rules follow
- Cause: Relative path
$script_dir/../skills/i-have-adhd/SKILL.mddoesn't resolve - Common trigger: Repository manually moved or cloned to non-standard location
Corrupted Front-Matter
- Symptom: Full file content printed (including metadata), or empty output
- Cause: AWK pattern fails to detect closing
---—missing newline, extra whitespace, or malformed delimiters - Note: The regex
^---[[:space:]]*$requires clean line endings
Undefined Environment Variables
- Symptom: Hook fails before script execution
- Cause:
$CLAUDE_PLUGIN_ROOTexpands empty inhooks.json, producingsh "" - Context: Rare in standard Claude Code installations; affects custom plugin loaders
Timeout Exceeded
- Symptom: Hook aborted mid-execution, status message hangs
- Cause: 5-second
timeoutinhooks.jsonexceeded by slow filesystem (network drives, encrypted volumes) or hanging AWK process
Enabling and Disabling the Always-On Hook
Enable Always-On Mode
# Create config directory if needed
mkdir -p "${CLAUDE_CONFIG_DIR:-$HOME/.claude}"
# Create the opt-in flag file
touch "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/.i-have-adhd-always"
Disable for Current Session Only
Send the plugin's recognized command within Claude Code:
stop adhd mode
Disable Permanently
rm "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/.i-have-adhd-always"
Debugging the Hook: Stand-Alone Reproduction
Test the mechanism independently of Claude Code:
#!/usr/bin/env sh
# Save as test-hook.sh and run from repository root
export CLAUDE_CONFIG_DIR="${CLAUDE_CONFIG_DIR:-$HOME/.claude}"
claude_dir="$CLAUDE_CONFIG_DIR"
flag_path="$claude_dir/.i-have-adhd-always"
echo "Checking flag at: $flag_path"
[ -f "$flag_path" ] || { echo "FAIL: Flag file missing"; exit 0; }
script_dir="hooks"
skill_path="$script_dir/../skills/i-have-adhd/SKILL.md"
[ -f "$skill_path" ] || { echo "FAIL: SKILL.md not found at $skill_path"; exit 0; }
body=$(awk '
NR == 1 && $0 ~ /^---[[:space:]]*$/ { in_fm = 1; next }
in_fm && $0 ~ /^---[[:space:]]*$/ { in_fm = 0; next }
!in_fm { print }
' "$skill_path")
[ -z "$body" ] && echo "WARNING: Extracted body is empty"
echo "--- OUTPUT ---"
printf 'ADHD MODE ACTIVE (always‑on). The ruleset below applies to every response.\n\n'
printf '%s...\n' "$skill_path"
printf '\n%s\n' "$body"
This expanded version adds diagnostic output while preserving the original logic structure from [hooks/always-on.sh](https://github.com/ayghri/i-have-adhd/blob/main/hooks/always-on.sh).
Key Files in the Always-On Mechanism
| File | Purpose |
|---|---|
[hooks/hooks.json](https://github.com/ayghri/i-have-adhd/blob/main/hooks/hooks.json) |
Registers the SessionStart hook with 5-second timeout |
[hooks/always-on.sh](https://github.com/ayghri/i-have-adhd/blob/main/hooks/always-on.sh) |
Implements flag checking, path resolution, and ruleset injection |
[skills/i-have-adhd/SKILL.md](https://github.com/ayghri/i-have-adhd/blob/main/skills/i-have-adhd/SKILL.md) |
Contains the ADHD formatting rules extracted by the hook |
~/.claude/.i-have-adhd-always |
User-created opt-in flag (not in repository) |
Summary
- The always-on hook is a POSIX shell script executed via Claude Code's
SessionStartevent - Activation requires creating
~/.claude/.i-have-adhd-always(or equivalent path under$CLAUDE_CONFIG_DIR) - The script is intentionally fail-safe: any error exits 0 to prevent blocking session start
- Silent failures occur when flag files are missing, paths are misconfigured, permissions are incorrect, or environment variables are undefined
- The mechanism parses [
skills/i-have-adhd/SKILL.md](https://github.com/ayghri/i-have-adhd/blob/main/skills/i-have-adhd/SKILL.md) with AWK to strip YAML front-matter before injection
Frequently Asked Questions
Why does the always-on hook fail silently instead of showing an error?
The hook exits with status 0 in all error conditions—missing flag, missing skill file, AWK failures—to ensure Claude Code sessions start successfully regardless of plugin state. This design prioritizes session reliability over debugging visibility. Users who haven't opted in (no flag file) shouldn't see errors, so the same silent behavior applies to actual failures. Check file existence and permissions manually when troubleshooting.
What file permissions does the always-on hook require?
The always-on.sh script needs read and execute permissions (chmod 755 or chmod +x). The flag file ~/.claude/.i-have-adhd-always only needs read permission (chmod 644 or touch defaults). The hook runs via sh explicitly, so execute permission on the script itself is technically optional but recommended. The skill file SKILL.md requires read access for the AWK extraction to succeed.
Can I relocate the always-on flag file to a different directory?
The flag file location is computed as ${CLAUDE_CONFIG_DIR:-$HOME/.claude}/.i-have-adhd-always. To relocate it, set the CLAUDE_CONFIG_DIR environment variable before Claude Code starts. The hook doesn't support arbitrary flag locations—it's hardcoded to this path pattern in [hooks/always-on.sh](https://github.com/ayghri/i-have-adhd/blob/main/hooks/always-on.sh).
How do I verify the always-on hook is actually running?
Run the hook script directly with tracing: sh -x hooks/always-on.sh from the repository root. Check for:
- Correct
$CLAUDE_CONFIG_DIRexpansion - Flag file detection (
[ -f "$flag_path" ]) - SKILL.md path resolution
- Non-empty
bodyvariable after AWK processing
In Claude Code, the status message "Checking i-have-adhd always-on flag..." appears briefly if hooks.json is registered—its absence suggests plugin loading issues rather than hook logic failures.
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 →