Skip to content
Development
Hook

Hooks

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

From plugin
planning-with-files
27k6 skills13 commands6 hooks
Install
> /plugin marketplace add OthmanAdi/planning-with-files
> /plugin install planning-with-files@planning-with-files

Ships with planning-with-files. Installing the plugin gets these hooks.

What fires, and when

SessionStart

Fires once when a session begins, and again after a context compaction. It is where a plugin sets up its environment, or restores state the compaction dropped.

  • Matchesstartup|resume|clear|compactsh

UserPromptSubmit

Fires before Claude sees each prompt you send. A plugin can use it to inject context, so the same instruction reaches the model every turn instead of only at session start.

  • sh

PreToolUse

  • MatchesWrite|Edit|Bash|Read|Glob|Grepsh

PostToolUse

  • MatchesWrite|Editsh

PreCompact

  • Matches*sh

Stop

  • sh
Read hooks/hooks.json

In the plugin's words

How planning-with-files describes its own hook set.

Planning with Files lifecycle integration for Claude Code

Where it lives

  • hooks/claude-hook.shGitHub
    Read the script
    #!/bin/sh
    # Claude Code plugin lifecycle dispatcher.
    #
    # Plugin execution is deliberately rooted only at CLAUDE_PLUGIN_ROOT. The
    # standalone skill keeps its own activation-scoped frontmatter hooks, while
    # this launcher is the single plugin-level lifecycle route.
    
    set -u
    
    [ "${PLANNING_DISABLED:-}" = "1" ] && exit 0
    
    EVENT="${1:-}"
    PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-}"
    [ -n "$PLUGIN_ROOT" ] || exit 0
    
    SCRIPTS_DIR="${PLUGIN_ROOT}/scripts"
    INJECT_PLAN="${SCRIPTS_DIR}/inject-plan.sh"
    GATE_STOP="${SCRIPTS_DIR}/gate-stop.sh"
    RESOLVE_PLAN_DIR="${SCRIPTS_DIR}/resolve-plan-dir.sh"
    SESSION_CATCHUP="${SCRIPTS_DIR}/session-catchup.py"
    FAST_PATH="${SCRIPTS_DIR}/inject-plan.py"
    
    # --- One interpreter instead of one fork per tool (v3.17.0). ---
    # Everything below this block answers an event by running resolve-plan-dir.sh
    # and inject-plan.sh, and those answer by forking: realpath, stat, sha256sum,
    # awk, tr, mktemp, head, tail, sed, wc, four Python starts, and a $(...)
    # around most of them. A UserPromptSubmit fire forks about 130 times. On Linux
    # and macOS a fork costs one to three milliseconds. Under Git Bash on Windows
    # it costs about 90 ms, so the same fire took seven to twelve seconds against
    # the 10 s hook timeout: Claude Code discarded the output, the plan never
    # reached the model, and every Bash, Read, Grep and Edit call waited five
    # more seconds in PreToolUse first.
    #
    # scripts/inject-plan.py is a byte-identical twin of the chain below
    # (tests/test_inject_plan_python_parity.py proves it against this file and
    # inject-plan.sh on every CI leg). When a CPython 3 is on PATH the event runs
    # there in one process. The twin exits 0 only when its stdout is the complete
    # answer; any other status, including an interpreter that cannot run it, falls
    # through to the reference chain below unchanged. PWF_FAST_PATH=0 forces the
    # reference chain. The Stop event never takes the fast path because it must
    # forward Claude's stdin payload to gate-stop.sh.
    #
    # The PATH walk below is pure shell on purpose: `$(command -v python3)` is
    # itself a fork, and the whole point of this block is to spend none. The
    # Microsoft Store python.exe/python3.exe aliases are skipped by path: they are
    # discoverable on PATH yet refuse to run a script. python3 is preferred over
    # python across the whole PATH so an old distro's Python 2 `python` is never
    # tried while a python3 exists further down.
    find_python() {
        FOUND_PYTHON=""
        for _fp_explicit in "${PWF_TRUSTED_PYTHON:-}" "${PYTHON_BIN:-}"; do
            [ -n "$_fp_explicit" ] || continue
            case "$_fp_explicit" in
                /*|[A-Za-z]:[\\/]*) ;;
                *) continue ;;
            esac
            case "$_fp_explicit" in
                *[Ww][Ii][Nn][Dd][Oo][Ww][Ss][Aa][Pp][Pp][Ss]*) continue ;;
            esac
            if [ -f "$_fp_explicit" ] && [ -x "$_fp_explicit" ]; then
                FOUND_PYTHON="$_fp_explicit"
                return 0
            fi
        done
        # set -u is active: an unset IFS or PATH must not kill the dispatcher
        # before the reference chain gets its turn.
        if [ "${IFS+set}" = set ]; then
            _fp_saved_ifs="$IFS"
            _fp_ifs_was_set=1
        else
            _fp_saved_ifs=""
            _fp_ifs_was_set=0
        fi
        for _fp_name in python3 python; do
            IFS=:
            set -f
            for _fp_dir in ${PATH-}; do
                case "$_fp_dir" in
                    /*) ;;
                    *) continue ;;
                esac
                case "$_fp_dir" in
                    *[Ww][Ii][Nn][Dd][Oo][Ww][Ss][Aa][Pp][Pp][Ss]*) continue ;;
                esac
                if [ -f "${_fp_dir}/${_fp_name}" ] && [ -x "${_fp_dir}/${_fp_name}" ]; then
                    FOUND_PYTHON="${_fp_dir}/${_fp_name}"
                    break
                fi
            done
            set +f
            if [ "$_fp_ifs_was_set" = 1 ]; then IFS="$_fp_saved_ifs"; else unset IFS; fi
            [ -n "$FOUND_PYTHON" ] && return 0
        done
        return 1
    }
    
    case "$EVENT" in
        session-start|user-prompt-submit|pre-tool-use|post-tool-use|pre-compact)
            # No planning state where the resolver will look and no selector to
            # validate: every route below answers with nothing, so answer with
            # nothing now, before any interpreter or fork. Byte-identical to the
            # reference chain, which reaches the same silence about ten forks
            # later. A set PLAN_ID or PWF_PLAN_ROOT still gets its refusal notice.
            if [ -z "${PLAN_ID:-}" ] && [ -z "${PWF_PLAN_ROOT:-}" ] \
                && [ ! -f task_plan.md ] && [ ! -d .planning ]; then
                exit 0
            fi
            if [ "${PWF_FAST_PATH:-}" != "0" ] && [ -f "$FAST_PATH" ] && find_python; then
                # -I: isolated mode, so the project directory is never on
                # sys.path and a repository's own secrets.py or hashlib.py cannot
                # be imported by a hook. -B: never write bytecode into the
                # plugin cache. PWF_SHELL_PWD hands the twin this shell's own
                # $PWD spelling so both routes derive the same turn-marker and
                # progress-guard slots; MSYS2_ENV_CONV_EXCL keeps Git Bash from
                # rewriting it into a Windows path on the way.
                #
                # The twin's stdout is deliberately not captured: $(...) costs
                # another fork, the very thing this block exists to remove. The
                # contract that makes this safe is the twin's: it writes nothing
                # until its answer is complete, and exits non-zero otherwise.
                if PWF_SHELL_PWD="$PWD" \
                    MSYS2_ENV_CONV_EXCL="${MSYS2_ENV_CONV_EXCL:+${MSYS2_ENV_CONV_EXCL};}PWF_SHELL_PWD" \
                    "$FOUND_PYTHON" -I -B "$FAST_PATH" "--claude-event=${EVENT}" 2>/dev/null; then
                    exit 0
                fi
            fi
            ;;
    esac
    
    # JSON-string encode bounded, trusted hook output without requiring Python or
    # Node. The planning scripts already bound injected project data; this removes
    # remaining JSON-forbidden control bytes and preserves line boundaries. Walk
    # characters directly because

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.

Ships withplanning-with-files

Persistent file-based planning for AI coding agents and long-running tasks. Crash-proof markdown plans, session recovery after /clear and compaction, per-turn re-injection against context rot, deterministic completion gate. Manus-style. Install from npm, the Claude Code plugin marketplace, or npx skills. Codex, Cursor, OpenCode, 60+ agents.

Get the whole plugin