# Pi Extension Architecture vs Claude Code Hook Architecture: A Technical Comparison

> Compare Pi's extension architecture with Claude Code's hook architecture. Understand event-driven SDKs vs static injection points for enhanced control and execution.

- Repository: [Ayoub Ghriss/i-have-adhd](https://github.com/ayghri/i-have-adhd)
- Tags: deep-dive
- Published: 2026-09-10

---

**Pi's extension model provides an event-driven SDK with fine-grained control over the conversation lifecycle, while Claude Code's hook model offers a static injection point that executes scripts at specific session milestones.**

The `ayghri/i-have-adhd` repository demonstrates how the same AI skill behaves differently under two distinct integration patterns. While both Pi (`pi-coding-agent`) and Claude Code allow third-party modifications to model behavior, they implement fundamentally different approaches to **Pi extension architecture vs Claude Code hook architecture**. Understanding these differences is crucial for developers building cross-platform AI agent skills.

## Registration and Manifest Declaration

The entry points for both architectures rely on JSON manifests, but their structures and capabilities diverge significantly.

### Pi Extension Registration

Pi discovers extensions through the **[`package.json`](https://github.com/ayghri/i-have-adhd/blob/main/package.json)** file under the dedicated `pi` field. The runtime loads TypeScript modules specified in the `extensions` array at startup.

```json
{
  "pi": {
    "extensions": ["./extensions/i-have-adhd.ts"]
  }
}

```

This declaration binds the extension to the Pi runtime immediately, loading the module defined in [`extensions/i-have-adhd.ts`](https://github.com/ayghri/i-have-adhd/blob/main/extensions/i-have-adhd.ts) and exposing its registered commands and flags to the session manager.

### Claude Code Hook Registration

Claude Code uses **[`hooks/hooks.json`](https://github.com/ayghri/i-have-adhd/blob/main/hooks/hooks.json)** to map lifecycle events to executable scripts. Rather than loading a module, Claude Code invokes a Node.js process when specific triggers occur.

```json
{
  "SessionStart": {
    "command": "node hooks/always-on.mjs"
  }
}

```

The hook system executes `hooks/always-on.mjs` only when a new session begins, with no mechanism to load persistent modules or register ongoing event listeners.

## Runtime API and Execution Context

The execution environments expose fundamentally different capabilities to skill developers.

**Pi's ExtensionAPI** provides a rich interface for runtime manipulation. Extensions import the `pi` object to register flags, commands, and event handlers:

```typescript
// From extensions/i-have-adhd.ts
pi.registerFlag("adhd", { 
  description: "ADHD-friendly output mode", 
  type: "boolean", 
  default: false 
});

pi.registerCommand("i-have-adhd", {
  description: "Toggle ADHD-friendly output",
  handler: async (args, ctx) => { /* toggle logic */ },
});

pi.on("input", async (event, ctx) => {
  const input = event.text.trim().toLowerCase();
  if (STOP_PHRASES.has(input)) { /* disable mode */ }
  return { action: "continue" };
});

```

**Claude Code's hook architecture** provides no comparable API. Hooks are plain scripts that communicate via **stdout** and execute in isolation:

```javascript
// From hooks/always-on.mjs
const flagPath = path.join(claudeDir, ".i-have-adhd-always");
if (!fs.existsSync(flagPath)) process.exit(0);

const skillPath = path.join(scriptDir, "..", "skills", "i-have-adhd", "SKILL.md");
const body = fs.readFileSync(skillPath, "utf8")
  .replace(/^---[\s\S]*?---/, "")
  .replace(/(?:\r?\n)+$/, "");

process.stdout.write(`ADHD MODE ACTIVE...\n\n${body}\n`);

```

The hook cannot access the session manager, register ongoing listeners, or modify context after the initial injection.

## State Management and Persistence

Persistent state handling reveals the architectural gap between the two systems.

Pi extensions maintain state across conversation turns using **`pi.appendEntry`** and context queries. The extension in [`extensions/i-have-adhd.ts`](https://github.com/ayghri/i-have-adhd/blob/main/extensions/i-have-adhd.ts) can check `contextMessages` or use [`extensions/context-compat.ts`](https://github.com/ayghri/i-have-adhd/blob/main/extensions/context-compat.ts) to verify if the rules are already active via `latestMarkerIsActive`. This allows the skill to toggle modes dynamically and survive across multiple inputs.

Claude Code hooks operate as **stateless, one-time injections**. The script in `hooks/always-on.mjs` reads the `.i-have-adhd-always` flag file only at execution time, prints the content of [`SKILL.md`](https://github.com/ayghri/i-have-adhd/blob/main/SKILL.md) to stdout, and terminates. Any subsequent changes require starting a new session to re-trigger the hook.

## Event Granularity and Lifecycle Control

The timing and frequency of skill execution differ dramatically.

Pi offers **fine-grained, event-driven control**. The extension can react to `session_start`, `input`, `session_tree`, and `session_compact` events, allowing real-time synchronization of context on every turn. When a user types `/skill:i-have-adhd` or a stop phrase, the `pi.on("input")` handler processes the command immediately.

Claude Code provides only **coarse-grained trigger points**. The architecture supports hooks like `SessionStart` defined in [`hooks/hooks.json`](https://github.com/ayghri/i-have-adhd/blob/main/hooks/hooks.json), but cannot react to intermediate user inputs or conversation turns without additional hook definitions. The skill lacks the ability to modify behavior mid-session.

## Cross-Platform Portability

Platform specificity varies between the two approaches.

Pi extensions follow the generic **ExtensionAPI** contract, making them portable across Pi and OMP (Open Model Platform) runtimes. The same [`extensions/i-have-adhd.ts`](https://github.com/ayghri/i-have-adhd/blob/main/extensions/i-have-adhd.ts) file works in both environments without modification.

Claude Code hooks depend on proprietary environment variables like **`CLAUDE_PLUGIN_ROOT`** and **`PLUGIN_ROOT`**, binding them specifically to Claude Code and Codex implementations. The script in `hooks/always-on.mjs` explicitly references `process.env.CLAUDE_CONFIG_DIR`, limiting portability to Claude's ecosystem.

## Summary

- **Pi's architecture** uses an event-driven ExtensionAPI defined in [`package.json`](https://github.com/ayghri/i-have-adhd/blob/main/package.json), allowing skills to register flags, commands, and handlers for events like `input` and `session_start` through [`extensions/i-have-adhd.ts`](https://github.com/ayghri/i-have-adhd/blob/main/extensions/i-have-adhd.ts).
- **Claude Code's architecture** relies on static hooks declared in [`hooks/hooks.json`](https://github.com/ayghri/i-have-adhd/blob/main/hooks/hooks.json), executing isolated scripts such as `hooks/always-on.mjs` at specific milestones like `SessionStart` via stdout injection.
- Pi extensions maintain persistent state using `pi.appendEntry` and `contextMessages`, enabling dynamic behavior changes throughout a session, while Claude Code hooks are stateless and run once per session.
- The Pi extension ecosystem emphasizes runtime control and cross-platform compatibility with OMP, whereas Claude Code hooks prioritize simple initialization scripts tied to specific environment variables like `CLAUDE_PLUGIN_ROOT`.

## Frequently Asked Questions

### Can a Claude Code hook react to user input after the session starts?

No. Claude Code hooks execute only at predefined points such as `SessionStart` as configured in [`hooks/hooks.json`](https://github.com/ayghri/i-have-adhd/blob/main/hooks/hooks.json). Unlike Pi's `pi.on("input")` event handler, Claude Code hooks cannot listen to or modify behavior based on subsequent user inputs without starting a new session.

### How does the Pi extension verify if ADHD mode is currently active?

The Pi extension uses [`extensions/context-compat.ts`](https://github.com/ayghri/i-have-adhd/blob/main/extensions/context-compat.ts) to query the session context via `latestMarkerIsActive` or `contextMessages`. This allows the extension to check for existing context entries and avoid duplicate injections, a capability unavailable in Claude Code's hook model.

### What file declares the extension entry point for the Pi runtime?

The **[`package.json`](https://github.com/ayghri/i-have-adhd/blob/main/package.json)** file declares Pi extensions under the `pi.extensions` field, typically referencing TypeScript files like [`./extensions/i-have-adhd.ts`](https://github.com/ayghri/i-have-adhd/blob/main/./extensions/i-have-adhd.ts). Claude Code uses a separate **[`hooks/hooks.json`](https://github.com/ayghri/i-have-adhd/blob/main/hooks/hooks.json)** file to map events to executable scripts.

### Why do Claude Code hooks read from SKILL.md instead of using a configuration API?

Claude Code hooks lack access to a configuration API or session manager. They must read files like [`SKILL.md`](https://github.com/ayghri/i-have-adhd/blob/main/SKILL.md) directly from the filesystem, strip frontmatter using regex, and write content to stdout. This static injection approach contrasts with Pi's dynamic `pi.registerFlag` system, which exposes structured configuration through the ExtensionAPI.