Skip to content
Development
Hook

Hooks

What scaffolding runs automatically, and when. A hook is a command Claude Code fires at a fixed moment, without you asking for it.

From plugin
scaffolding
1639 skills13 agents21 commands25 hooks

Claude Code

> /plugin marketplace add komluk/scaffolding
> /plugin install scaffolding@komluk-scaffolding

Where it lives

  • hooks/action-log.shGitHub
    Read the script
    #!/usr/bin/env bash
    # action-log.sh — Stop + SessionEnd hook: episodic action log for LOCAL sessions.
    #
    # Reads the CLI transcript (transcript_path from stdin) and appends ONE metadata-only,
    # redacted JSONL line per tool call to .scaffolding/conversations/<session_id>/actions/,
    # then re-renders conversations/<id>/timeline.md. Never stores tool output, file
    # contents, prompts or MCP results (see hooks/lib/action-log.mjs). Incremental via a
    # byte cursor, so only new lines are parsed. Always exits 0 and never blocks.
    #
    # DEFAULT OFF (opt-in): a plugin update never starts logging by itself. Enable with
    # ACTION_LOG_ENABLED=1|true|yes|on in the environment, or per repo with the sentinel
    # .scaffolding/.actionlog.
    #
    # Skips when: not opted in, SCAFFOLDING_TASK_ID set (platform run — the backend
    # extracts post-run), SCAFFOLDING_ACTION_LOG=off (hard off, wins over opt-in),
    # .scaffolding/.noactionlog exists, no .scaffolding/conversations/, or the lock is
    # busy (next Stop catches up). Needs node >= 22; without node it does nothing.
    set +e
    
    INPUT=$(cat 2>/dev/null)
    [ -n "${SCAFFOLDING_TASK_ID:-}" ] && exit 0
    [ "${SCAFFOLDING_ACTION_LOG:-}" = "off" ] && exit 0
    
    DIR="${CLAUDE_PROJECT_DIR:-$PWD}"
    SC="$DIR/.scaffolding"
    [ -f "$SC/.noactionlog" ] && exit 0
    case "$(printf '%s' "${ACTION_LOG_ENABLED:-}" | tr '[:upper:]' '[:lower:]')" in
        1|true|yes|on) ;;
        *) [ -f "$SC/.actionlog" ] || exit 0 ;;
    esac
    [ -d "$SC/conversations" ] || exit 0
    
    LIB="${BASH_SOURCE[0]%/*}"; [[ "$LIB" == "${BASH_SOURCE[0]}" ]] && LIB=.; LIB="$LIB/lib/action-log.mjs"
    [ -f "$LIB" ] || exit 0
    command -v node >/dev/null 2>&1 || { echo "scaffolding: node not found, action-log not enforced" >&2; exit 0; }
    
    # transcript_path / session_id / hook event come from stdin JSON; only UUID-ish ids are accepted
    # (the three lines are printed only for a valid session_id and a *.jsonl transcript_path).
    FIELDS=$(printf '%s' "$INPUT" | node "$LIB" hook-fields 2>/dev/null)
    SID=$(printf '%s\n' "$FIELDS" | sed -n 1p)
    TP=$(printf '%s\n' "$FIELDS" | sed -n 2p)
    FINAL=$(printf '%s\n' "$FIELDS" | sed -n 3p)
    [ -n "$SID" ] && [ -f "$TP" ] || exit 0
    
    CONV="$SC/conversations/$SID"
    mkdir -p "$CONV/actions" 2>/dev/null || exit 0
    
    ARGS=(local --transcript "$TP" --session-id "$SID" --conv-dir "$CONV" --project-dir "$DIR")
    [ "$FINAL" = "1" ] && ARGS+=(--final)
    
    run() {
        timeout 5 node "$LIB" "${ARGS[@]}" 2>/dev/null
        timeout 5 node "$LIB" render --conv-dir "$CONV" >/dev/null 2>&1
    }
    
    if command -v flock >/dev/null 2>&1; then
        ( flock -n 9 || exit 0; run ) 9>"$CONV/actions/.lock"
    else
        run
    fi
    exit 0
    
  • hooks/auto-init-check.shGitHub
    Read the script
    #!/usr/bin/env bash
    # auto-init-check.sh — SessionStart soft auto-init (startup/resume).
    #
    # Detects whether the current project is initialized for scaffolding:
    #   1. a project-root CLAUDE.md that contains the routing protocol OR imports
    #      the central one via an `@...CLAUDE.md` line (e.g. @~/.claude/CLAUDE.md), AND
    #   2. .claude/settings.json
    # If either is MISSING, injects additionalContext advising the user/Claude to
    # run /init-scaffolding.
    #
    # STRICTLY IDEMPOTENT & NON-DESTRUCTIVE:
    #   - NEVER overwrites or creates CLAUDE.md or settings.json.
    #   - NEVER clobbers user content.
    #   - Only best-effort creates the safe .scaffolding/ skeleton (|| true), the
    #     same harmless directory memory-project-id.sh already relies on.
    # Always exits 0; SessionStart JSON additionalContext like session-start-protocol.sh.
    set +e
    
    [ -n "${SCAFFOLDING_TASK_ID:-}" ] && exit 0   # platform run: project CLAUDE.md isn't loaded, this advisory is meaningless
    
    dir="${CLAUDE_PROJECT_DIR:-$PWD}"
    
    # --- Detection (read-only) ---
    has_routing=0
    if [ -f "$dir/CLAUDE.md" ] && grep -qE 'subagent_type="scaffolding:|^@.*CLAUDE\.md' "$dir/CLAUDE.md" 2>/dev/null; then
        has_routing=1
    fi
    
    has_settings=0
    if [ -f "$dir/.claude/settings.json" ]; then
        has_settings=1
    fi
    
    # Already initialized -> nothing to advise. Still emit a valid (empty-context)
    # SessionStart object so the hook is well-formed.
    if [ "$has_routing" = "1" ] && [ "$has_settings" = "1" ]; then
        exit 0
    fi
    
    # --- Safe skeleton only (never touches CLAUDE.md / settings.json) ---
    mkdir -p "$dir/.scaffolding/agent-memory/shared" 2>/dev/null || true
    mkdir -p "$dir/.scaffolding/agent-memory/agents" 2>/dev/null || true
    mkdir -p "$dir/.scaffolding/conversations" 2>/dev/null || true
    mkdir -p "$dir/.scaffolding/worktrees" 2>/dev/null || true
    
    # --- Build advisory message ---
    missing=""
    [ "$has_routing" = "0" ] && missing="${missing}CLAUDE.md routing protocol, "
    [ "$has_settings" = "0" ] && missing="${missing}.claude/settings.json, "
    missing="${missing%, }"
    
    ctx="scaffolding: this project is not fully initialized (missing: ${missing}). Run /init-scaffolding to install the routing protocol and hooks so agent delegation works here. This is advisory only — no files were overwritten; the safe .scaffolding/ skeleton was created if absent."
    
    # SessionStart additionalContext injection (JSON-escape via node hook-io quote, with fallback).
    LIB="${BASH_SOURCE[0]%/*}"; [[ "$LIB" == "${BASH_SOURCE[0]}" ]] && LIB=.; LIB="$LIB/lib"
    printf '{"hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":%s}}\n' \
      "$(printf '%s' "$ctx" | node "$LIB/hook-io.mjs" quote 2>/dev/null || printf '"%s"' "$ctx")"
    
    exit 0
    
  • hooks/block-destructive-rm.shGitHub
    Read the script
    #!/usr/bin/env bash
    # PreToolUse hook (Bash): Block destructive rm -rf / find -delete on protected paths, and rm fed by xargs.
    # Exit 2 = abort (block the tool call), Exit 0 = allow.
    # Protected (after ~ / $HOME expansion and path normalisation): / and /*, /etc /var /usr (and below),
    # /home /root /boot /opt /srv /bin /sbin /lib*, $HOME and its top-level entries, . and .. (any depth), .git.
    # Sees every line, quoting, \rm, /bin/rm, sudo/env/command wrappers, bash -c and xargs.
    # Decision logic: hooks/lib/guards.mjs (rmGuard).
    
    set -uo pipefail
    
    INPUT=$(cat)
    # Fast path: none of rm / unlink / -delete / shred / truncate, and no quote, backslash, $, ` or glob that could hide one.
    shopt -s nocasematch
    RE=$'(^|[^a-z])(rm|unlink|shred|truncate)([^a-z]|$)|-delete|[\\\\\'\`$*?[]|\\{[^"}[:space:]]'
    [[ "$INPUT" =~ $RE ]] || exit 0
    
    # Node is the JSON helper (hooks/lib/hook-io.mjs). Without it the hook cannot decide: fail open.
    LIB="${BASH_SOURCE[0]%/*}"; [[ "$LIB" == "${BASH_SOURCE[0]}" ]] && LIB=.; LIB="$LIB/lib"
    command -v node >/dev/null 2>&1 || { echo "scaffolding: node not found, block-destructive-rm not enforced" >&2; exit 0; }
    
    # Malformed JSON or a non-string field -> no message -> allow (exit 0, never exit 1 = warn-but-proceed).
    # node itself failing (crash, out of memory, killed) after the fast path matched -> block: fail closed.
    MSG=$(printf '%s' "$INPUT" | node "$LIB/hook-io.mjs" guard rm); RC=$?
    if [ "$RC" -ne 0 ]; then
        echo "BLOCKED: block-destructive-rm could not check this command (node exited $RC). Retry, or split the command." >&2
        exit 2
    fi
    if [ -n "$MSG" ]; then
        printf '%s\n' "$MSG" >&2
        exit 2
    fi
    exit 0
    
  • hooks/block-env-write.shGitHub
    Read the script
    #!/usr/bin/env bash
    # PreToolUse hook (Edit|Write|MultiEdit and Bash): Block writes to .env / .env.* files (case-insensitive).
    # Exit 2 = abort (block the tool call), Exit 0 = allow.
    # Edit/Write: the basename of tool_input.file_path. Bash: redirects (> >> >| &> 2>), tee, cp/mv/install
    # destinations, sed -i and perl -i, on every line and inside bash -c / sudo / env wrappers.
    # Decision logic: hooks/lib/guards.mjs (envWriteGuard).
    
    set -uo pipefail
    
    INPUT=$(cat)
    # Fast path: nothing that could spell .env: no "env", no ".e" / ".<glob>", no quote, backslash, $, ` or glob.
    shopt -s nocasematch
    RE=$'env|\\.[e*?[{]|[\\\\\'\`$*?[]|\\{[^"}[:space:]]'
    [[ "$INPUT" =~ $RE ]] || exit 0
    
    # Node is the JSON helper (hooks/lib/hook-io.mjs). Without it the hook cannot decide: fail open.
    LIB="${BASH_SOURCE[0]%/*}"; [[ "$LIB" == "${BASH_SOURCE[0]}" ]] && LIB=.; LIB="$LIB/lib"
    command -v node >/dev/null 2>&1 || { echo "scaffolding: node not found, block-env-write not enforced" >&2; exit 0; }
    
    # Malformed JSON or a non-string field -> no message -> allow (exit 0, never exit 1 = warn-but-proceed).
    # node itself failing (crash, out of memory, killed) after the fast path matched -> block: fail closed.
    MSG=$(printf '%s' "$INPUT" | node "$LIB/hook-io.mjs" guard env); RC=$?
    if [ "$RC" -ne 0 ]; then
        echo "BLOCKED: block-env-write could not check this command (node exited $RC). Retry, or split the command." >&2
        exit 2
    fi
    if [ -n "$MSG" ]; then
        printf '%s\n' "$MSG" >&2
        exit 2
    fi
    exit 0
    
  • hooks/block-force-push.shGitHub
    Read the script
    #!/usr/bin/env bash
    # PreToolUse hook (Bash): Block force pushes and remote deletion of main/master.
    # Exit 2 = abort (block the tool call), Exit 0 = allow.
    # Blocks --force / --force-with-lease / --force-if-includes, -f in any short-flag group (-uf), a +refspec,
    # --mirror, and --delete / -d / :branch on main or master. Sees git global options (-c, -C, --no-pager),
    # every line, wrappers (sudo, env) and bash -c. Decision logic: hooks/lib/guards.mjs (pushGuard).
    
    set -uo pipefail
    
    INPUT=$(cat)
    # Fast path: no "push" / git alias config, and no quote, backslash, $, ` or glob that could split one.
    shopt -s nocasematch
    RE=$'push|alias\\.|autocorrect|[\\\\\'\`$*?[]|\\{[^"}[:space:]]'
    [[ "$INPUT" =~ $RE ]] || exit 0
    
    # Node is the JSON helper (hooks/lib/hook-io.mjs). Without it the hook cannot decide: fail open.
    LIB="${BASH_SOURCE[0]%/*}"; [[ "$LIB" == "${BASH_SOURCE[0]}" ]] && LIB=.; LIB="$LIB/lib"
    command -v node >/dev/null 2>&1 || { echo "scaffolding: node not found, block-force-push not enforced" >&2; exit 0; }
    
    # Malformed JSON or a non-string field -> no message -> allow (exit 0, never exit 1 = warn-but-proceed).
    # node itself failing (crash, out of memory, killed) after the fast path matched -> block: fail closed.
    MSG=$(printf '%s' "$INPUT" | node "$LIB/hook-io.mjs" guard push); RC=$?
    if [ "$RC" -ne 0 ]; then
        echo "BLOCKED: block-force-push could not check this command (node exited $RC). Retry, or split the command." >&2
        exit 2
    fi
    if [ -n "$MSG" ]; then
        printf '%s\n' "$MSG" >&2
        exit 2
    fi
    exit 0
    
  • hooks/block-subagent.shGitHub
    Read the script
    #!/usr/bin/env bash
    # PreToolUse hook (matcher: Task): Block denylisted subagent types.
    # Exit 2 = block the tool call, Exit 0 = allow.
    # Denylist: general-purpose in ANY case (General-Purpose, GENERAL-PURPOSE), and exactly 'explore'
    # (lower case only: the built-in 'Explore' quick-search agent stays allowed). No trimming or prefix matching:
    # scaffolding:<agent>, plan and all other custom agents are allowed.
    
    set +e
    
    # Node is the JSON helper (hooks/lib/hook-io.mjs). Without it the hook cannot decide: fail open.
    LIB="${BASH_SOURCE[0]%/*}"; [[ "$LIB" == "${BASH_SOURCE[0]}" ]] && LIB=.; LIB="$LIB/lib"
    command -v node >/dev/null 2>&1 || { echo "scaffolding: node not found, block-subagent not enforced" >&2; exit 0; }
    
    # Reason: hook-io yields an empty string on malformed JSON so the hook exits 0
    # (allow) rather than exit 1 (warn-but-proceed). Read tool_input.subagent_type.
    SUBAGENT_TYPE=$(node "$LIB/hook-io.mjs" get tool_input.subagent_type 2>/dev/null)
    
    # nocasematch + case, not a bash-4 lower-case expansion: macOS ships bash 3.2. explore stays a separate, case-sensitive test.
    DENY=false
    shopt -s nocasematch
    case "$SUBAGENT_TYPE" in general-purpose) DENY=true ;; esac
    shopt -u nocasematch
    [ "$SUBAGENT_TYPE" = "explore" ] && DENY=true
    if $DENY; then
        echo "BLOCKED: subagent_type '$SUBAGENT_TYPE' is denylisted by the scaffolding protocol. Use a scaffolding:<agent> (e.g. scaffolding:developer, scaffolding:analyst) instead. 'general-purpose' conflicts with the custom agents; 'explore' is for quick file searches only, never planning/analysis." >&2
        exit 2
    fi
    
    exit 0
    
  • hooks/completion-nudge.shGitHub
  • hooks/count-conversations.shGitHub
  • hooks/file-size-warn.shGitHub
  • hooks/file-staleness-check.shGitHub
  • hooks/file-staleness-update.shGitHub
  • hooks/memory-ingest-mark.shGitHub
  • hooks/memory-ingest.shGitHub
  • hooks/memory-project-id.shGitHub
  • hooks/memory-recall.shGitHub
  • hooks/nfs-sync.shGitHub
  • hooks/notify.shGitHub
  • hooks/post-edit-format.shGitHub
  • hooks/post-edit-review.shGitHub
  • hooks/pre-commit-validation.shGitHub
  • hooks/refactor-guard.shGitHub
  • hooks/refresh-mcp-token.shGitHub
  • hooks/session-start-protocol.shGitHub
  • hooks/worktree-create.shGitHub
  • hooks/worktree-remove.shGitHub

All 25 scripts are listed above. The source is inlined for 6 of them, starting with whatever hooks.json actually runs. See all of them in the repo.

Read the script before you install anything that runs on your machine. This is the one part of a plugin that acts without being asked.

@komluk
Ships withscaffolding

Spec-driven multi-agent orchestration for Claude Code — pure markdown, zero backend, runs on the stock runtime. 13 agents, 39 skills, 21 commands, 20 hooks, per-phase model tiers, opt-in lifecycle hooks, optional cross-device semantic memory.

1639 skills13 agents
Get the whole plugin

Stars on GitHub

Momentum

stars in total
16
Star history of komluk/scaffolding on GitHub, a chart by star-history.com

More plugins that ship hooks.

superpowers

superpowers

A development methodology for coding agents that makes the agent brainstorm before building, follow plans, run parallel work and finish branches in a disciplined way. You get fewer skipped steps and more consistent results from your coding assistant.

15 skills1 hook311k
@obra@obra
Install Plugin
ponytail

ponytail

A Claude Code plugin with skills for auditing a whole repo, reviewing a change, listing deferred shortcut comments, and showing measured savings and a quick command reference.

6 skills10 hooks188k
@dietrichgebert@dietrichgebert
Install Plugin
agent-skills

agent-skills

Walks a software change from a written spec through design, testing, code review, CI/CD, and release, applying the matching engineering skill at each stage. Useful for developers using Claude Code on full development workflows.

25 skills+3112k
@addyosmani@addyosmani
Install Plugin
planning-with-files

planning-with-files

Persistent file-based planning for AI coding agents on long-running, multi-step tasks. It keeps task_plan.md, findings.md and progress.md on disk, so work can resume after /clear or context compaction.

6 skills+228k
@othmanadi@othmanadi
Install Plugin
gsd-core

gsd-core

A spec-driven development workflow for Claude Code that guides a project through planning, phases, milestone audits and fixes. Includes skills to generate tests from UAT criteria and to audit completed work.

72 skills+311k
@open-gsd@open-gsd
Install Plugin
superpowers-zh

superpowers-zh

Chinese-language skill pack for Claude Code that adds workflow guidance for brainstorming, TDD, debugging, and code review, plus Chinese conventions for commits, documentation, and domestic Git platforms like Gitee and Coding.net.

21 skills1 hook8.5k
@jnmetacode@jnmetacode
Install Plugin
socraticode

socraticode

Gives Claude Code semantic search, dependency graph and architecture context over a codebase, so an AI assistant can explore and understand a project's code. Includes skills for indexing a project and managing its file watching.

2 skills+33.4k
@giancarloerra@giancarloerra
Install Plugin
cocoindex-code

cocoindex-code

A lightweight code search engine that uses AST-based semantic search with local embeddings and incremental indexing. It helps Claude Code users find code faster and more efficiently.

1 skill1 MCP+13k
@cocoindex-io@cocoindex-io
Install Plugin

Questions about the hooks in scaffolding.

Which coding agents can use the hooks in scaffolding?
The hooks in scaffolding install in Claude Code and Cursor: each runs Claude Code hooks from .claude/settings.json, so copy the hooks there.