feat: inject ponytail ruleset into subagents via SubagentStart hook (#254)
* feat: inject ponytail ruleset into subagents via SubagentStart hook SessionStart additionalContext is parent-thread only, so every Task-spawned agent ran ponytail-unaware. Add a SubagentStart hook that injects the active ruleset into each subagent, reusing getPonytailInstructions. Native Claude needs the hookSpecificOutput JSON form (not raw stdout), so writeHookOutput grows a SubagentStart branch; readMode exposes the live flag. Workflow- and team-spawned coverage is undocumented upstream; verify in a fresh session once installed. Closes #252 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H1dMag33yz1Kf1jS24Aira * fix: address QA round 1 — make hook tests hermetic + cover Codex SubagentStart QA round 1 (panel + deepseek-v4-pro) found two real test issues: - The new subagent block (and the pre-existing claudeEnv block) used a no-op `delete env.PLUGIN_DATA`; run() spreads process.env, so a PLUGIN_DATA / COPILOT_PLUGIN_DATA leaked from the shell would steer writeHookOutput into the codex/copilot branch and silently mis-fire the native-Claude assertions. Fixed at the source: neutralize both vars once at the top, like CLAUDE_CONFIG_DIR. - The Codex SubagentStart branch (claude-codex-hooks.json is shared by both plugin manifests) had zero coverage. Added a codex-path assertion. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H1dMag33yz1Kf1jS24Aira * fix: use PowerShell-safe command form for SubagentStart hook Match the post-#265 'node ...; exit 0' form used by the sibling hooks. The old 'command -v node ... || exit 0' form fails tests/hooks-windows.test.js (POSIX-guard and non-blocking asserts) once this branch merges onto current main. --------- Co-authored-by: Shane McCarron <shane.mccarron@corvexconnect.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Emeriko <dietrich.gebert@gmail.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
Shane McCarron
Emeriko
parent
9d0118df34
commit
b9fa564429
@@ -14,6 +14,19 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"SubagentStart": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/ponytail-subagent.js\"; exit 0",
|
||||
"commandWindows": "if (Get-Command node -ErrorAction SilentlyContinue) { node \"$env:CLAUDE_PLUGIN_ROOT\\hooks\\ponytail-subagent.js\" }",
|
||||
"timeout": 5,
|
||||
"statusMessage": "Loading ponytail mode..."
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"UserPromptSubmit": [
|
||||
{
|
||||
"hooks": [
|
||||
|
||||
@@ -21,6 +21,15 @@ function clearMode() {
|
||||
try { fs.unlinkSync(statePath); } catch (e) {}
|
||||
}
|
||||
|
||||
// Live mode written by activate/mode-tracker. Absent flag = ponytail off.
|
||||
function readMode() {
|
||||
try {
|
||||
return fs.readFileSync(statePath, 'utf8').trim() || null;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function writeHookOutput(event, mode, context = '') {
|
||||
if (isCopilot) {
|
||||
// Copilot reads additionalContext on SessionStart; ignores output elsewhere.
|
||||
@@ -39,6 +48,13 @@ function writeHookOutput(event, mode, context = '') {
|
||||
process.stdout.write(JSON.stringify(output));
|
||||
return;
|
||||
}
|
||||
// Native Claude: SessionStart accepts raw stdout, but SubagentStart needs the
|
||||
// hookSpecificOutput JSON form or the context is dropped.
|
||||
if (event === 'SubagentStart') {
|
||||
process.stdout.write(JSON.stringify(
|
||||
{ hookSpecificOutput: { hookEventName: event, additionalContext: context } }));
|
||||
return;
|
||||
}
|
||||
process.stdout.write(context);
|
||||
}
|
||||
|
||||
@@ -46,6 +62,7 @@ module.exports = {
|
||||
clearMode,
|
||||
isCodex,
|
||||
isCopilot,
|
||||
readMode,
|
||||
setMode,
|
||||
writeHookOutput,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env node
|
||||
// ponytail — Claude Code SubagentStart hook
|
||||
//
|
||||
// SessionStart context is parent-thread only and never reaches subagents, so
|
||||
// without this every Task-spawned agent runs ponytail-unaware (issue #252).
|
||||
// When ponytail mode is active, inject the same ruleset into each subagent.
|
||||
|
||||
const { getPonytailInstructions } = require('./ponytail-instructions');
|
||||
const { readMode, writeHookOutput } = require('./ponytail-runtime');
|
||||
|
||||
const mode = readMode();
|
||||
|
||||
// Absent flag or off → ponytail isn't active; inject nothing.
|
||||
if (!mode || mode === 'off') {
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
try {
|
||||
writeHookOutput('SubagentStart', mode, getPonytailInstructions(mode));
|
||||
} catch (e) {
|
||||
// Silent fail — a stdout error at hook exit must not surface as a hook failure.
|
||||
}
|
||||
Reference in New Issue
Block a user