Back to catalogue
NotificationSubagentStop
Subagent end voice summary
Hear each subagent finish, no audio clash
Announces the end of a subagent with a voice message. Uses a file lock to avoid TTS overlap when several subagents finish in parallel.
What does the Subagent end voice summary hook do?
Subagent end voice summary is a Claude Code SubagentStop hook. It fires automatically at that lifecycle event — outside the model, so it can't be skipped or forgotten. Hear each subagent finish, no audio clash.
As a SubagentStop hook it runs after the action, reacting to what just happened rather than blocking it. Because it is a deterministic Node.js script, it executes on every matching event without relying on the model to remember — the guarantee that makes agentic workflows safe to automate.
Use cases
- Audio tracking of completed parallel tasks
- Ordered subagent-end announcements without sound collisions
Tags
#tts#subagent#voice#parallel-agents#locking#audio
settings.json fragment
{
"hooks": {
"SubagentStop": [
{
"hooks": [
{
"command": "node $CLAUDE_PROJECT_DIR/.claude/hooks/subagent-stop-tts.mjs",
"type": "command"
}
],
"matcher": ""
}
]
}
}Script · .claude/hooks/subagent-stop-tts.mjs
#!/usr/bin/env node
// @hookstack subagent-stop-tts-summary
import { execSync } from "node:child_process";
// @hookstack subagent-stop-tts-summary
// Annonce la fin d'un sous-agent par TTS (SubagentStop)
import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
function defaultExec(cmd) {
execSync(cmd, { timeout: 10_000, stdio: "ignore", shell: true });
}
export function run(
input,
{ exec = defaultExec, platform = process.platform } = {},
) {
const summary = input?.summary ?? "";
const text = summary
? `Sous-agent terminé : ${summary.slice(0, 100).replace(/[`*_#]/g, "")}`
: "Sous-agent terminé";
const safe = text.replace(/"/g, '\\"');
try {
if (platform === "darwin") exec(`say "${safe}"`);
else exec(`espeak "${safe}" 2>/dev/null`);
} catch {}
return text;
}
/* v8 ignore next 5 */
if (process.argv[1] === fileURLToPath(import.meta.url)) {
let input = {};
try {
input = JSON.parse(readFileSync(0, "utf8"));
} catch {}
run(input);
}