S
SubagentStop
HookFires on
SubagentStopWhen a delegated subagent completes its work.
Where this sits in a session
- Session start→
- Prompt submitted→
- Prompt expansion→
- Before tool use→
- After tool use→
- Notification→
- Before compaction→
- Subagent finished→
- Turn finished→
- Session end
Can block — This hook is able to deny the action or send Claude back to work.
What it runs
Executed automatically when the event fires — no prompt, no confirmation.
command
${CLAUDE_PLUGIN_ROOT}/scripts/hooks/assemble-on-stop.shInstall it yourself
Paste into .claude/settings.json to run this hook without installing the whole plugin. Adjust the paths to point at your own copy of the scripts.
settings.json
{
"hooks": {
"SubagentStop": [
{
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/hooks/assemble-on-stop.sh"
}
]
}
]
}
}Source
Read this before installing — it runs on your machine with your permissions.
#!/usr/bin/env bash
# Vibes Stop hook — runs assemble.js at end of turn when app.jsx is newer
# than index.html. No-op outside Vibes projects.
#
# Exit codes:
# 0 — nothing to do OR assembly succeeded OR circuit breaker tripped
# 2 — assembly failed; stderr contains actionable error for the agent
set -euo pipefail
# 1. Walk up from cwd to find the nearest Vibes project (vibes.json + app.jsx).
# Stop at $HOME or / to avoid walking out of the user's working area.
PROJECT_DIR="$(pwd)"
HOME_BOUNDARY="${HOME:-/}"
while [ "$PROJECT_DIR" != "/" ] && [ "$PROJECT_DIR" != "$HOME_BOUNDARY" ]; do
if [ -f "$PROJECT_DIR/vibes.json" ] && [ -f "$PROJECT_DIR/app.jsx" ]; then
break
fi
PROJECT_DIR="$(dirname "$PROJECT_DIR")"
done
# Not in a Vibes project → silent no-op
[ -f "$PROJECT_DIR/vibes.json" ] && [ -f "$PROJECT_DIR/app.jsx" ] || exit 0
# Plugin root is set by Claude Code at hook invocation time. In dev mode
# (`claude --plugin .`) it can be missing — exit silently rather than crash,
# otherwise this hook would fail every turn and re-introduce the gap it closes.
VIBES_ROOT="${CLAUDE_PLUGIN_ROOT:-}"
if [ -z "$VIBES_ROOT" ]; then
echo "assemble-on-stop: CLAUDE_PLUGIN_ROOT not set, skipping" >&2
exit 0
fi
APP="$PROJECT_DIR/app.jsx"
HTML="$PROJECT_DIR/index.html"
# 2. Idempotency — skip if index.html is newer than app.jsx
if [ -f "$HTML" ] && [ "$HTML" -nt "$APP" ]; then
exit 0
fi
# 3. Run assembly
cd "$PROJECT_DIR"
ASSEMBLE_OUTPUT=$(bun "$VIBES_ROOT/scripts/assemble.js" app.jsx index.html 2>&1) || {
# Circuit breaker — if same app.jsx content has failed twice in a row, give up
RETRY_FILE="$PROJECT_DIR/.vibes/assemble-retry"
CURRENT_HASH=$(shasum -a 256 "$APP" | cut -d' ' -f1)
LAST_HASH=$(cat "$RETRY_FILE" 2>/dev/null || echo "")
if [ "$CURRENT_HASH" = "$LAST_HASH" ]; then
rm -f "$RETRY_FILE"
echo "Vibes assembly failed twice on identical app.jsx. Allowing turn to end so the user can intervene." >&2
echo "$ASSEMBLE_OUTPUT" >&2
exit 0
fi
mkdir -p "$(dirname "$RETRY_FILE")"
echo "$CURRENT_HASH" > "$RETRY_FILE"
echo "Vibes assembly failed. Fix app.jsx — the assembler will re-run when you finish your next turn." >&2
echo "$ASSEMBLE_OUTPUT" >&2
exit 2
}
# Success — clear retry state
rm -f "$PROJECT_DIR/.vibes/assemble-retry" 2>/dev/null || true
Shipped by 1 plugin
Installing any of these installs this hook.
Reviews
Log in to leave a review.
No reviews yet — be the first.
Explore related
Other things in this space — across every part of the ecosystem, not just hooks.