How Ponytail Handles Output Formats for Different AI Agents

Ponytail detects the host environment through specific environment variables and adapts its console output serialization so that each AI agent receives context in its expected format.

The DietrichGebert/ponytail repository implements a flexible hook system that customizes output formats for different agents to ensure seamless integration. By inspecting runtime environment markers, Ponytail determines whether it is running inside GitHub Copilot, OpenAI Codex, Qoder, or native Claude, then structures its JSON payloads accordingly.

Environment Detection and Format Logic

The adaptation logic resides primarily in hooks/ponytail-runtime.js, where the writeHookOutput function branches based on boolean flags derived from process.env variables. The activation hook ponytail-activate.js determines the operational mode, generates instruction text via hooks/ponytail-instructions.js, and delegates serialization to writeHookOutput.

GitHub Copilot (VS Code)

Ponytail identifies Copilot by checking for process.env.COPILOT_PLUGIN_DATA or an agent-plugins path within process.env.CLAUDE_PLUGIN_ROOT (flagged internally as isCopilot).

For this agent, output is strictly limited to SessionStart events. The function writes either a JSON object containing { "additionalContext": "…" } or an empty object {} as raw text. All other events produce no console output, preventing noise during the coding session.

// Inside ponytail-runtime.js → isCopilot branch
process.stdout.write(JSON.stringify(
  event === 'SessionStart' && context ? { additionalContext: context } : {}
));

OpenAI Codex

When process.env.PLUGIN_DATA is present (isCodex), Ponytail emits a JSON object containing a systemMessage field formatted as PONYTAIL:${mode.toUpperCase()}. If the hook provides supplementary text, a hookSpecificOutput object is appended containing hookEventName and additionalContext properties.

// Inside ponytail-runtime.js → isCodex branch
const output = { systemMessage: `PONYTAIL:${mode.toUpperCase()}` };
if (context) {
  output.hookSpecificOutput = {
    hookEventName: event,
    additionalContext: context,
  };
}
process.stdout.write(JSON.stringify(output));

Qoder

Detection occurs via process.env.QODER_SESSION_ID (isQoder). Qoder receives a JSON payload identical to Codex's hookSpecificOutput structure but omits the systemMessage field entirely. This minimalist approach ensures Qoder receives only the contextual data without mode identifiers.

// Inside ponytail-runtime.js → isQoder branch
const output = {};
if (context) {
  output.hookSpecificOutput = {
    hookEventName: event,
    additionalContext: context,
  };
}
process.stdout.write(JSON.stringify(output));

Native Claude (Claude Code)

If none of the above environment variables are set, Ponytail assumes a native Claude environment. This handler differentiates between event types: for SubagentStart events, it outputs a JSON wrapper containing hookSpecificOutput, while all other events stream the raw context string directly to stdout.

// Inside ponytail-runtime.js → native Claude branch
if (event === 'SubagentStart') {
  process.stdout.write(JSON.stringify(
    { hookSpecificOutput: { hookEventName: event, additionalContext: context } }));
  return;
}
process.stdout.write(context);

Summary

  • Environment-based detection allows Ponytail to identify GitHub Copilot, Codex, Qoder, and native Claude through specific process.env variables.
  • Format adaptation occurs in hooks/ponytail-runtime.js, where the writeHookOutput function selects JSON schemas or raw text based on the detected host.
  • Copilot receives additionalContext only during SessionStart events, while Codex includes a systemMessage prefix indicating the operational mode.
  • Qoder consumes the same contextual payload as Codex without the system-level message wrapper.
  • Native Claude switches between JSON-wrapped output for subagent initiation and plain text for standard events.

Frequently Asked Questions

How does Ponytail detect which AI agent is running?

Ponytail inspects specific environment variables in process.env. It checks for COPILOT_PLUGIN_DATA or CLAUDE_PLUGIN_ROOT to identify Copilot, PLUGIN_DATA for Codex, and QODER_SESSION_ID for Qoder. When none of these markers are present, the system defaults to native Claude behavior.

Why does GitHub Copilot only receive output during SessionStart events?

According to the source code in hooks/ponytail-runtime.js (lines 19-27), Copilot's integration only consumes the additionalContext field during session initialization. The isCopilot branch explicitly filters for event === 'SessionStart', returning early or writing empty objects for all subsequent events to avoid interfering with the editor's output stream.

What distinguishes the Codex output format from the Qoder format?

Both agents receive a hookSpecificOutput object containing hookEventName and additionalContext. However, Codex additionally receives a systemMessage field (e.g., "PONYTAIL:FULL") that communicates the current operational mode. Qoder's payload, as implemented in lines 69-80 of ponytail-runtime.js, deliberately omits this field to match its consumption protocol.

Where does the instruction content originate that gets formatted for these agents?

The ruleset text is generated by hooks/ponytail-instructions.js. The ponytail-activate.js hook builds this content and passes it as the context argument to writeHookOutput. This ensures that regardless of the serialization format—whether Copilot's minimal JSON or Codex's wrapped payload—each agent receives identical underlying instructions tailored to its parser expectations.

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 →