# Node.js Lifecycle Hooks Used by Ponytail Plugins: The Complete Developer Guide

> Learn about the Node.js lifecycle hooks SessionStart UserPromptSubmit and SubagentStart used by Ponytail plugins to customize AI agent behavior. A complete developer guide.

- Repository: [DietrichGebert/ponytail](https://github.com/DietrichGebert/ponytail)
- Tags: deep-dive
- Published: 2026-08-28

---

**Ponytail plugins implement three specific Node.js lifecycle hooks—SessionStart, UserPromptSubmit, and SubagentStart—to intercept and modify AI agent behavior at critical execution points.**

Ponytail is an open-source framework by DietrichGebert that extends Claude, Codex, Qoder, and Copilot through plugin hooks written in Node.js. Understanding the Node.js lifecycle hooks used by Ponytail plugins enables developers to initialize modes, parse user commands, and inject contextual rulesets across different host agents.

## The Three Core Lifecycle Hooks

Ponytail recognizes exactly three lifecycle events. Each corresponds to a specific phase in the host agent's execution cycle and receives different input payloads via `stdin` or environment variables.

### SessionStart Hook

The **SessionStart** hook fires at the beginning of a native Claude session—the first time the agent starts. It receives no input payload via `stdin`, but can access environment variables like `PONYTAIL_DEFAULT_MODE`. Developers use this hook to initialize the default Ponytail mode, emit status-line nudges, or write the active mode flag file.

### UserPromptSubmit Hook

The **UserPromptSubmit** hook fires every time the user submits a prompt, including the first interaction. It receives a JSON object `{ prompt: string, ... }` from `stdin`. This hook parses `@ponytail ...` commands to switch modes dynamically and inject the generated ruleset as additional context for the current prompt.

### SubagentStart Hook

The **SubagentStart** hook fires when sub-agents launch—whether tool-use agents, planners, or Qoder sub-agents. It receives an optional JSON payload that may contain an `agent_type` field from `stdin`. The hook injects the current Ponytail ruleset into the sub-agent context, optionally filtered by the `PONYTAIL_SUBAGENT_MATCHER` environment variable.

## How the Hook Runtime Works

The runtime logic resides in [`hooks/ponytail-runtime.js`](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-runtime.js). This module exports three critical functions: `readMode()`, `setMode()`, and `writeHookOutput(event, mode, context)`.

Hook scripts follow a consistent pattern:

- Detect the host platform (`isCopilot`, `isCodex`, `isQoder`) via environment variables
- Read the current mode from the `.ponytail-active` flag file using `readMode()`
- Produce output through `writeHookOutput()` to ensure host-compatible formatting

The runtime automatically formats output as plain text or JSON (with `systemMessage` or `hookSpecificOutput` fields) based on the detected host platform.

## Implementing Each Lifecycle Hook

### SessionStart Hook Implementation

The [`hooks/ponytail-activate.js`](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-activate.js) script handles initialization by reading the default mode and persisting it:

```javascript
// hooks/ponytail-activate.js (SessionStart hook)
const { getDefaultMode, writeHookOutput } = require('./ponytail-config');
const { setMode } = require('./ponytail-runtime');

const mode = getDefaultMode();          // e.g. "ultra" from env or config
setMode(mode);                           // write the flag file
writeHookOutput('SessionStart', mode, `PONYTAIL MODE ACTIVE — level: ${mode}`);

```

### UserPromptSubmit Hook Implementation

The [`hooks/ponytail-mode-tracker.js`](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-mode-tracker.js) parses `@ponytail` commands from user input:

```javascript
// hooks/ponytail-mode-tracker.js (UserPromptSubmit hook)
const { readMode, setMode, clearMode } = require('./ponytail-runtime');

const { prompt } = JSON.parse(fs.readFileSync(0, 'utf8'));

if (/^@ponytail\s+(\w+)/i.test(prompt)) {
  const newMode = RegExp.$1.toLowerCase();
  setMode(newMode);
  writeHookOutput('UserPromptSubmit', newMode,
    `PONYTAIL MODE CHANGED — level: ${newMode}`);
} else if (/^stop\s+ponytail$/i.test(prompt)) {
  clearMode();
  writeHookOutput('UserPromptSubmit', 'off', 'PONYTAIL MODE OFF');
}

```

### SubagentStart Hook Implementation

The [`hooks/ponytail-subagent.js`](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-subagent.js) handles context injection with optional type matching:

```javascript
// hooks/ponytail-subagent.js (SubagentStart hook)
const { readMode } = require('./ponytail-runtime');

const mode = readMode();
if (!mode) process.exit(0);   // ponytail disabled → stay silent

const envMatcher = process.env.PONYTAIL_SUBAGENT_MATCHER;
if (envMatcher) {
  const { agent_type } = JSON.parse(fs.readFileSync(0, 'utf8') || '{}');
  try {
    const re = new RegExp(envMatcher, 'i');
    if (agent_type && !re.test(agent_type)) process.exit(0);
  } catch (_) { /* invalid regex → fall back to inject everywhere */ }
}

writeHookOutput('SubagentStart', mode,
  `PONYTAIL MODE ACTIVE — level: ${mode}`);

```

## Summary

- Ponytail implements exactly three Node.js lifecycle hooks: **SessionStart**, **UserPromptSubmit**, and **SubagentStart**
- Hook scripts reside in the `hooks/` directory and leverage utilities from [`hooks/ponytail-runtime.js`](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-runtime.js)
- **SessionStart** initializes modes using environment variables like `PONYTAIL_DEFAULT_MODE`
- **UserPromptSubmit** parses JSON input from `stdin` to detect `@ponytail` commands
- **SubagentStart** supports scoped injection via the `PONYTAIL_SUBAGENT_MATCHER` environment variable

## Frequently Asked Questions

### What triggers the SessionStart hook in Ponytail?

The SessionStart hook fires at the beginning of a native Claude session when the agent is first started. It receives no JSON payload via `stdin`, relying instead on environment variables like `PONYTAIL_DEFAULT_MODE` to determine the initial configuration state.

### How do I parse user commands in the UserPromptSubmit hook?

Read the JSON payload from `stdin` using `fs.readFileSync(0, 'utf8')`, then access the `prompt` property. Use regex patterns like `/^@ponytail\s+(\w+)/i` to detect mode-switching commands, then call `setMode()` from [`hooks/ponytail-runtime.js`](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-runtime.js) to persist changes to the `.ponytail-active` flag file.

### Can I scope Ponytail rules to specific sub-agent types?

Yes. The SubagentStart hook checks the `PONYTAIL_SUBAGENT_MATCHER` environment variable against the `agent_type` field from the JSON `stdin` payload. If the regex doesn't match the agent type, the script exits silently without injecting the ruleset into that specific sub-agent.

### Where are the hook scripts located in the repository?

All hook implementations reside in the `hooks/` directory of the DietrichGebert/ponytail repository. Key files include [`ponytail-runtime.js`](https://github.com/DietrichGebert/ponytail/blob/main/ponytail-runtime.js) (core utilities), [`ponytail-activate.js`](https://github.com/DietrichGebert/ponytail/blob/main/ponytail-activate.js) (SessionStart), [`ponytail-mode-tracker.js`](https://github.com/DietrichGebert/ponytail/blob/main/ponytail-mode-tracker.js) (UserPromptSubmit), and [`ponytail-subagent.js`](https://github.com/DietrichGebert/ponytail/blob/main/ponytail-subagent.js) (SubagentStart).