# hooks/always-on.mjs in i-have-adhd: Purpose, Execution, and Opt-In Behavior

> Discover the purpose of hooks/always-on.mjs in i-have-adhd. Learn how this SessionStart hook conditionally injects rulesets into Claude or Codex sessions via opt-in flag.

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

---

**TLDR:** `hooks/always-on.mjs` is a **SessionStart hook** that conditionally injects the i-have-adhd ruleset into every Claude or Codex session via stdout only when the user has created an opt-in flag file.

The `i-have-adhd` plugin for Claude Code provides ADHD-friendly response guidelines through an automatic always-on mode. The `hooks/always-on.mjs` file is the core mechanism that enables this behavior, checking for explicit user consent and prepending the rule set to the session prompt. According to the `ayghri/i-have-adhd` source code, this script runs silently in the background and only modifies the session when explicitly enabled.

## What Is the Purpose of hooks/always-on.mjs?

The primary purpose of `hooks/always-on.mjs` is to act as a **conditional prompt injector**. It verifies that the user has opted into persistent ADHD mode before loading and emitting the full ruleset defined in [`skills/i-have-adhd/SKILL.md`](https://github.com/ayghri/i-have-adhd/blob/main/skills/i-have-adhd/SKILL.md).

### Opt-In Flag Detection

The script first checks for a flag file at `$CLAUDE_CONFIG_DIR/.i-have-adhd-always`, defaulting to `~/.claude/.i-have-adhd-always`. If the file does **not** exist, the hook exits immediately with return code `0`, leaving the session completely unchanged. This design ensures that the plugin remains strictly opt-in and never modifies sessions without explicit user consent.

### Ruleset Injection and Banner Output

When the flag file is present, `hooks/always-on.mjs` reads the skill file, strips any leading YAML front-matter, and writes a banner message followed by the rule text to **STDOUT**. The banner notifies the user that "ADHD MODE ACTIVE (always-on)" and includes instructions for disabling the mode. Because stdout from the hook becomes part of the initial prompt, every subsequent response in the session follows the ADHD-friendly guidelines.

## When Is hooks/always-on.mjs Executed?

The hook executes during the **SessionStart** event, which fires before any model response is generated.

### SessionStart Hook Registration

Execution is declared in [`hooks/hooks.json`](https://github.com/ayghri/i-have-adhd/blob/main/hooks/hooks.json), which maps the `SessionStart` event to a Node.js one-liner. This one-liner dynamically imports `hooks/always-on.mjs` from the plugin root using `process.env.CLAUDE_PLUGIN_ROOT` or `process.env.PLUGIN_ROOT`. The Claude-Code or Codex runtime evaluates this entry every time a new session context begins.

### Session Events That Trigger the Hook

As implemented in `ayghri/i-have-adhd`, the hook runs on every session initialization, including:

- Initial startup
- Session resume
- Clear operations
- Compact operations

Because the hook fires **before** response generation, the ADHD ruleset is guaranteed to be present in the system prompt from the first interaction onward.

## How the Hook Logic Works

The internal logic of `hooks/always-on.mjs` can be reproduced with the following minimal implementation. This script mirrors the actual source code behavior, including flag checking, front-matter stripping, and stdout emission:

```javascript
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { fileURLToPath } from "node:url";

const claudeDir = process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), ".claude");
const flagPath  = path.join(claudeDir, ".i-have-adhd-always");

// Bail out if the opt-in flag is missing
if (!fs.existsSync(flagPath)) process.exit(0);

// Locate the skill file relative to this script
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
const skillPath = path.join(scriptDir, "..", "skills", "i-have-adhd", "SKILL.md");
if (!fs.existsSync(skillPath)) process.exit(0);

// Remove leading YAML front-matter (--- … ---) if present
const body = fs.readFileSync(skillPath, "utf8")
  .replace(/^---[^\S\r\n]*\r?\n[\s\S]*?\r?\n---[^\S\r\n]*(?:\r?\n|$)/, "")
  .replace(/(?:\r?\n)+$/, "");

// Emit the banner + rules to stdout (this becomes the session prompt)
process.stdout.write(
  `ADHD MODE ACTIVE (always-on). The ruleset below applies to every response. ` +
  `"stop adhd mode" turns it off for this session; delete ${flagPath} to turn always-on off for good.\n\n${body}\n`
);

```

## Key Files in the Hook Pipeline

Several files in the `ayghri/i-have-adhd` repository work together to define when the hook fires and what content it injects:

- **`hooks/always-on.mjs`** — Implements the SessionStart hook that conditionally injects the ADHD ruleset.
- **[`hooks/hooks.json`](https://github.com/ayghri/i-have-adhd/blob/main/hooks/hooks.json)** — Declares the `SessionStart` hook and launches `always-on.mjs` via a Node one-liner.
- **[`skills/i-have-adhd/SKILL.md`](https://github.com/ayghri/i-have-adhd/blob/main/skills/i-have-adhd/SKILL.md)** — Contains the ADHD-friendly response rules that the hook strips of front-matter and injects.
- **[`tests/test_always_on_hooks.py`](https://github.com/ayghri/i-have-adhd/blob/main/tests/test_always_on_hooks.py)** — Unit tests verifying opt-in handling, front-matter stripping, and silent execution when disabled.

## Summary

- `hooks/always-on.mjs` is a **SessionStart hook** that conditionally prepends the i-have-adhd ruleset to the session prompt.
- The hook only activates when the user creates the **opt-in flag file** at `~/.claude/.i-have-adhd-always`.
- It is triggered by the **SessionStart** event declared in [`hooks/hooks.json`](https://github.com/ayghri/i-have-adhd/blob/main/hooks/hooks.json) and runs before any response is generated.
- The script reads [`skills/i-have-adhd/SKILL.md`](https://github.com/ayghri/i-have-adhd/blob/main/skills/i-have-adhd/SKILL.md), strips YAML front-matter, and emits the rules to **STDOUT** with an activation banner.
- If the flag file is missing, the script exits silently with code `0` and leaves the session unchanged.

## Frequently Asked Questions

### What happens if I don't create the opt-in flag file?

If the flag file does not exist at `$CLAUDE_CONFIG_DIR/.i-have-adhd-always`, `hooks/always-on.mjs` exits immediately with return code `0` and produces no output. The session starts normally without any ADHD-specific prompt modifications.

### How do I disable the always-on ADHD mode?

You can disable the mode for the current session by typing "stop adhd mode" in the chat. To permanently disable always-on mode, delete the flag file at `~/.claude/.i-have-adhd-always` (or the equivalent path defined in `$CLAUDE_CONFIG_DIR`).

### Does the hook modify any configuration files permanently?

No. The hook only reads the flag file and the skill file; it does not write to or modify any configuration files. Its entire effect is transient, lasting only for the current session via stdout injection.

### What triggers the SessionStart hook besides opening a new session?

According to the [`hooks/hooks.json`](https://github.com/ayghri/i-have-adhd/blob/main/hooks/hooks.json) configuration in `ayghri/i-have-adhd`, the SessionStart event also fires when resuming a session, clearing the context, or compacting the conversation history. The hook runs on each of these events to ensure the ruleset remains active.