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

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 file under the dedicated pi field. The runtime loads TypeScript modules specified in the extensions array at startup.

{
  "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 and exposing its registered commands and flags to the session manager.

Claude Code Hook Registration

Claude Code uses 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.

{
  "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:

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

// 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 can check contextMessages or use 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 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, 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 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, allowing skills to register flags, commands, and handlers for events like input and session_start through extensions/i-have-adhd.ts.
  • Claude Code's architecture relies on static hooks declared in 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. 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 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 file declares Pi extensions under the pi.extensions field, typically referencing TypeScript files like ./extensions/i-have-adhd.ts. Claude Code uses a separate 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 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.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →