ClaudeSuperPower
S

SubagentStop

Hook

Fires on

SubagentStop

When a delegated subagent completes its work.

Where this sits in a session

  1. Session start
  2. Prompt submitted
  3. Prompt expansion
  4. Before tool use
  5. After tool use
  6. Notification
  7. Before compaction
  8. Subagent finished
  9. Turn finished
  10. Session end

Can blockThis 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.sh

Install 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.