# How the Always-On Hook Mechanism Works in the i-have-adhd Claude Plugin (Plus Why It Fails)

> Discover how the always-on hook mechanism in the i-have-adhd Claude plugin works and understand common failure points. Learn why it might not be activating for you.

- Repository: [Ayoub Ghriss/i-have-adhd](https://github.com/ayghri/i-have-adhd)
- Tags: internals
- Published: 2026-08-05

---

**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)](https://github.com/ayghri/i-have-adhd/blob/main/hooks/hooks.json):

```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)](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

```sh
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

```sh
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

```sh
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)](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

```sh
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`](https://github.com/ayghri/i-have-adhd/blob/main/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

```sh
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-always` to verify existence

### Wrong Config Directory

- **Symptom:** Flag file exists elsewhere but rules don't inject
- **Cause:** `$CLAUDE_CONFIG_DIR` overrides 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.sh`](https://github.com/ayghri/i-have-adhd/blob/main/always-on.sh) lacks execute permission, or flag file isn't readable
- **Check:** `ls -l hooks/always-on.sh` and verify `r-x` permissions

### 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.md` doesn'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_ROOT` expands empty in [`hooks.json`](https://github.com/ayghri/i-have-adhd/blob/main/hooks.json), producing `sh ""`
- **Context:** Rare in standard Claude Code installations; affects custom plugin loaders

### Timeout Exceeded

- **Symptom:** Hook aborted mid-execution, status message hangs
- **Cause:** 5-second `timeout` in [`hooks.json`](https://github.com/ayghri/i-have-adhd/blob/main/hooks.json) exceeded by slow filesystem (network drives, encrypted volumes) or hanging AWK process

## Enabling and Disabling the Always-On Hook

### Enable Always-On Mode

```bash

# 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:

```text
stop adhd mode

```

### Disable Permanently

```bash
rm "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/.i-have-adhd-always"

```

## Debugging the Hook: Stand-Alone Reproduction

Test the mechanism independently of Claude Code:

```bash
#!/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)](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)](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)](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)](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 `SessionStart` event
- 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)](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`](https://github.com/ayghri/i-have-adhd/blob/main/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`](https://github.com/ayghri/i-have-adhd/blob/main/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)](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_DIR` expansion
- Flag file detection (`[ -f "$flag_path" ]`)
- SKILL.md path resolution
- Non-empty `body` variable after AWK processing

In Claude Code, the status message "Checking i-have-adhd always-on flag..." appears briefly if [`hooks.json`](https://github.com/ayghri/i-have-adhd/blob/main/hooks.json) is registered—its absence suggests plugin loading issues rather than hook logic failures.