Node.js Lifecycle Hooks Used by Ponytail Plugins: The Complete Developer Guide
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. 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-activeflag file usingreadMode() - 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 script handles initialization by reading the default mode and persisting it:
// 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 parses @ponytail commands from user input:
// 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 handles context injection with optional type matching:
// 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 fromhooks/ponytail-runtime.js - SessionStart initializes modes using environment variables like
PONYTAIL_DEFAULT_MODE - UserPromptSubmit parses JSON input from
stdinto detect@ponytailcommands - SubagentStart supports scoped injection via the
PONYTAIL_SUBAGENT_MATCHERenvironment 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 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 (core utilities), ponytail-activate.js (SessionStart), ponytail-mode-tracker.js (UserPromptSubmit), and ponytail-subagent.js (SubagentStart).
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →