How to Use PONYTAIL_SUBAGENT_MATCHER to Scope Ruleset Injection in Ponytail
Set the PONYTAIL_SUBAGENT_MATCHER environment variable to a case‑insensitive regular expression to limit Ponytail’s ruleset injection to sub‑agents whose agent_type matches the pattern.
The Ponytail plugin injects instruction rulesets into sub‑agents spawned by AI hosts like Claude, Codex, and Qoder. By default, this injection applies to every sub‑agent unconditionally, but the PONYTAIL_SUBAGENT_MATCHER environment variable lets you restrict injection to specific agent types. This guide covers the matching implementation in hooks/ponytail-subagent.js and practical configuration examples for supported hosts.
How PONYTAIL_SUBAGENT_MATCHER Works
Regex Compilation and Validation
At startup, the hook reads process.env.PONYTAIL_SUBAGENT_MATCHER and compiles it with the case‑insensitive flag (i). In hooks/ponytail-subagent.js (lines 33‑38), the code attempts to instantiate a RegExp from the variable. If the pattern is malformed, the error is caught silently and the hook falls back to the default behavior: injecting into every sub‑agent.
Runtime Matching Logic
When the host spawns a sub‑agent, the hook receives a JSON payload via stdin containing the agent_type field (lines 60‑66). The logic follows these steps:
- Parse the payload. If JSON parsing fails or the
agent_typekey is missing, the hook proceeds with injection (fail‑open) to avoid blocking the sub‑agent. - Test the regex. If
agent_typeexists and does not satisfy the compiled regex, the hook exits silently withprocess.exit(0)(lines 66‑70). - Inject the ruleset. If the regex matches, the
inject()function is called, sending theSubagentStartpayload containing Ponytail’s instructions.
Default Behavior When Unset
When PONYTAIL_SUBAGENT_MATCHER is unset or contains an invalid pattern, the hook skips the stdin read entirely and calls inject() synchronously (lines 45‑48). This preserves backward compatibility with Windows hosts and ensures the ruleset is always injected when no matcher is configured.
Configuration Examples
Bash Wrapper for Claude or Codex
Export the matcher before launching your AI host to restrict injection to agents with types containing "general" or "plan":
export PONYTAIL_SUBAGENT_MATCHER='general|plan'
# Launch the host—Ponytail will only inject into matching sub-agents
claude
Use anchors for exact matches. The pattern ^general$ matches only the agent type general, while general (unanchored) matches general‑purpose, general‑coding, etc.
Node.js Test Script
Validate your regex programmatically by simulating the hook’s stdin payload:
const { spawnSync } = require('child_process');
const path = require('path');
const env = {
...process.env,
PONYTAIL_SUBAGENT_MATCHER: '^general$' // Exact match only
};
const result = spawnSync(
process.execPath,
[path.join(__dirname, 'hooks', 'ponytail-subagent.js')],
{
env,
input: JSON.stringify({ agent_type: 'general' }), // Matching payload
encoding: 'utf8',
}
);
console.log(result.stdout); // → Contains SubagentStart JSON
Change agent_type to plan in the input to verify the hook exits silently for non‑matching types.
Qoder Hook Configuration
Add the environment variable to Qoder’s hook configuration in hooks/qoder-hooks.json:
{
"UserPromptSubmit": {
"command": "node $HOME/.qoder/plugins/ponytail/hooks/ponytail-subagent.js",
"env": {
"PONYTAIL_SUBAGENT_MATCHER": "explore|general"
}
}
}
With this configuration, Qoder spawns sub‑agents that receive Ponytail’s ruleset only if their agent_type contains "explore" or "general".
Regex Pattern Guidelines
The matcher is unanchored and case‑insensitive by design:
explore|generalmatches anyagent_typethat contains either substring regardless of case.^general$forces an exact match, excluding variants likegeneral‑coding.- Invalid regex patterns (e.g., unclosed parentheses) cause the hook to ignore the variable and inject everywhere, preventing accidental lockouts.
Because the implementation is fail‑open, missing agent_type fields in the platform payload result in unconditional injection. This ensures Ponytail’s instructions remain visible even if the host platform changes its message format.
Summary
- Set
PONYTAIL_SUBAGENT_MATCHERto a JavaScript‑compatible regular expression to filter which sub‑agents receive Ponytail’s ruleset. - Case‑insensitive matching is enforced automatically; anchoring (
^and$) is optional for exact matches. - Fail‑open behavior protects against misconfiguration: invalid regexes or missing
agent_typefields result in unconditional injection. - Source location: The core logic resides in
hooks/ponytail-subagent.js(lines 33‑70), with validation tests intests/hooks.test.js.
Frequently Asked Questions
What happens if PONYTAIL_SUBAGENT_MATCHER is unset?
When the environment variable is undefined, the hook skips regex compilation and stdin processing entirely, injecting the ruleset into every sub‑agent synchronously. This matches the original Windows‑compatible behavior.
How do I match exact agent types only?
Use start‑of‑string (^) and end‑of‑string ($) anchors in your pattern. For example, ^general$ matches only the exact string general, whereas general (unanchored) also matches general‑purpose or my‑general‑agent.
What happens if the regex is malformed?
If PONYTAIL_SUBAGENT_MATCHER contains invalid syntax (e.g., [a-z), the hook catches the SyntaxError during RegExp construction and falls back to injecting into every sub‑agent. This prevents a bad regex from accidentally blocking all sub‑agent activity.
Does this work with all AI hosts?
Yes. The matcher operates inside hooks/ponytail-subagent.js, which is invoked by the host’s sub‑agent spawn hook. As long as the host conforms to the standard payload format (JSON with agent_type on stdin), the filtering works identically for Claude, Codex, Qoder, and other supported platforms.
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 →