Skip to content
Development
Hook

Hooks

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

From plugin
fable-mode
343 skills1 agent2 commands7 hooks

Claude Code

> /plugin marketplace add rennf93/opus-fable-playbook
> /plugin install fable-mode@opus-fable-playbook

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|clear|compact|resume"${CLAUDE_PLUGIN_ROOT}/hooks/session-start.sh"

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.

  • "${CLAUDE_PLUGIN_ROOT}/hooks/prompt-nudge.sh"

Stop

  • "${CLAUDE_PLUGIN_ROOT}/hooks/stop-gate.sh"

SubagentStop

  • "${CLAUDE_PLUGIN_ROOT}/hooks/stop-gate.sh" subagent

PreToolUse

  • MatchesBash"${CLAUDE_PLUGIN_ROOT}/hooks/bash-discipline.sh"

PostToolUse

  • MatchesBash"${CLAUDE_PLUGIN_ROOT}/hooks/honesty-nudge.sh"

PreCompact

  • Matchesmanual|auto"${CLAUDE_PLUGIN_ROOT}/hooks/precompact.sh"
Read hooks/hooks.json

Where it lives

  • hooks/bash-discipline.shRunsGitHub
    Read the script
    #!/usr/bin/env bash
    # PreToolUse[Bash]: deny pure shell file-reads; dedicated tools exist.
    set -u
    DIR="$(cd "$(dirname "$0")" && pwd)"
    # shellcheck source=hooks/lib/telemetry.sh
    . "$DIR/lib/telemetry.sh"
    
    INPUT="$(cat)" || exit 0
    CMD="$(printf '%s' "$INPUT" | python3 -c \
      'import json,sys; print(json.load(sys.stdin).get("tool_input",{}).get("command",""))' \
      2>/dev/null || true)"
    [ -z "$CMD" ] && exit 0
    
    # Pipelines, compounds, redirects, heredocs are legitimate — allow.
    printf '%s' "$CMD" | grep -qE '\||&&|;|>|<<' && exit 0
    
    DENY=0
    printf '%s' "$CMD" | grep -qE '^[[:space:]]*(cat|head|tail|less|more)[[:space:]]' && DENY=1
    printf '%s' "$CMD" | grep -qE '^[[:space:]]*sed[[:space:]]+-n[[:space:]]' && DENY=1
    [ "$DENY" -eq 0 ] && exit 0
    
    SESSION="$(printf '%s' "$INPUT" | python3 -c \
      'import json,sys; print(json.load(sys.stdin).get("session_id","unknown"))' \
      2>/dev/null || true)"
    fable_telemetry "bash-discipline" "shell-read" "$SESSION"
    
    cat <<'JSON'
    {"hookSpecificOutput": {"hookEventName": "PreToolUse", "permissionDecision": "deny", "permissionDecisionReason": "Fable tool discipline: use the dedicated Read/Grep tools instead of shell file-reads (cat/head/tail/less/sed -n). Read is paginated and line-numbered; Grep searches without loading whole files."}}
    JSON
    exit 0
    
  • hooks/honesty-nudge.shRunsGitHub
    Read the script
    #!/usr/bin/env bash
    # PostToolUse[Bash]: when output shows failures, nudge verbatim reporting.
    set -u
    DIR="$(cd "$(dirname "$0")" && pwd)"
    # shellcheck source=hooks/lib/telemetry.sh
    . "$DIR/lib/telemetry.sh"
    
    INPUT="$(cat)" || exit 0
    RESP="$(printf '%s' "$INPUT" | python3 -c \
      'import json,sys; print(json.dumps(json.load(sys.stdin).get("tool_response","")))' \
      2>/dev/null || true)"
    [ -z "$RESP" ] || [ "$RESP" = '""' ] && exit 0
    
    HIT=0
    printf '%s' "$RESP" | grep -qE 'FAILED |= FAILURES =|test result: FAILED|--- FAIL|AssertionError|Traceback \(most recent call last\)' && HIT=1
    printf '%s' "$RESP" | grep -qE 'Tests:[^"]*failed' && HIT=1
    [ "$HIT" -eq 0 ] && exit 0
    
    SESSION="$(printf '%s' "$INPUT" | python3 -c \
      'import json,sys; print(json.load(sys.stdin).get("session_id","unknown"))' \
      2>/dev/null || true)"
    fable_telemetry "honesty-nudge" "failure-output" "$SESSION"
    
    cat <<'JSON'
    {"hookSpecificOutput": {"hookEventName": "PostToolUse", "additionalContext": "A command just reported failures. Fable honesty rule: report this outcome verbatim (the actual failing output) in your final message; do not summarize it as mostly-working or claim success."}}
    JSON
    exit 0
    
  • hooks/precompact.shRunsGitHub
    Read the script
    #!/usr/bin/env bash
    # PreCompact: shape what survives compaction.
    set -u
    cat > /dev/null || true
    cat <<'EOF'
    Compaction guidance (fable-mode): the summary must preserve, outcome-first:
    (1) current task state and remaining work, (2) what was verified, with the
    actual results, (3) any failures not yet reported to the user, verbatim,
    (4) pending user decisions, (5) paths of files being modified.
    EOF
    exit 0
    
  • hooks/prompt-nudge.shRunsGitHub
    Read the script
    #!/usr/bin/env bash
    # UserPromptSubmit: <=40-token doctrine nudge; question-shape heuristic.
    set -u
    INPUT="$(cat)" || exit 0
    PROMPT="$(printf '%s' "$INPUT" | python3 -c \
      'import json,sys; print(json.load(sys.stdin).get("prompt",""))' 2>/dev/null || true)"
    [ -z "$PROMPT" ] && exit 0
    case "$PROMPT" in /*) exit 0 ;; esac
    
    TRIMMED="$(printf '%s' "$PROMPT" | sed 's/[[:space:]]*$//')"
    FIRST="$(printf '%s' "$PROMPT" | awk '{print tolower($1); exit}')"
    LOWER="$(printf '%s' "$TRIMMED" | tr '[:upper:]' '[:lower:]')"
    case "$TRIMMED" in *\?) Q=1 ;; *) Q=0 ;; esac
    case "$FIRST" in
      why|what|how|is|does|should|can|are|do|where|when|who|which) Q=1 ;;
    esac
    case "$LOWER" in
      # imperative-investigate-then-report prompts ("run the tests and tell
      # me where this project stands") are assess-only even though they
      # don't start with a question word or end in "?".
      *where*stand*) Q=1 ;;
    esac
    
    if [ "${Q:-0}" = "1" ]; then
      printf 'This prompt is question-shaped: deliver your assessment; do not change code unless asked.'
    else
      printf 'Fable reminders: lead the final message with the outcome; finish work instead of narrating it; parallelize independent tool calls; delegate broad searches.'
    fi
    printf '\n'
    exit 0
    
  • hooks/session-start.shRunsGitHub
    Read the script
    #!/usr/bin/env bash
    # SessionStart: inject the doctrine card; flag inactive output style.
    set -u
    DIR="$(cd "$(dirname "$0")" && pwd)"
    cat > /dev/null || true   # drain stdin
    
    CARD="$DIR/lib/doctrine-card.md"
    [ -f "$CARD" ] && cat "$CARD"
    
    STYLE="$(python3 -c "import json,os;print(json.load(open(os.path.expanduser('~/.claude/settings.json'))).get('outputStyle',''))" 2>/dev/null || true)"
    if [ "$STYLE" != "Fable" ]; then
      printf '\nNote: the Fable output style is not set in user settings. If this session should run fable-mode fully, suggest the user run /output-style fable (or set "outputStyle": "Fable" in settings).\n'
    fi
    exit 0
    
  • hooks/stop-gate.shRunsGitHub
    Read the script
    #!/usr/bin/env bash
    # Stop/SubagentStop gate: block turn endings that promise instead of do.
    # Usage: stop-gate.sh [subagent]   Fail-open: any internal error => exit 0.
    set -u
    DIR="$(cd "$(dirname "$0")" && pwd)"
    # shellcheck source=hooks/lib/telemetry.sh
    . "$DIR/lib/telemetry.sh"
    
    INPUT="$(cat)" || exit 0
    py() { printf '%s' "$INPUT" | python3 -c "$1" 2>/dev/null || true; }
    
    ACTIVE="$(py 'import json,sys; print(json.load(sys.stdin).get("stop_hook_active", False))')"
    [ "$ACTIVE" = "True" ] && exit 0
    
    SESSION="$(py 'import json,sys; print(json.load(sys.stdin).get("session_id","unknown"))')"
    LAST="$(printf '%s' "$INPUT" | python3 "$DIR/lib/last_message.py" 2>/dev/null)" || exit 0
    [ -z "$LAST" ] && exit 0
    
    # Final paragraph = last blank-line-separated block (awk paragraph mode).
    FINAL="$(printf '%s' "$LAST" | awk -v RS='' 'END{print}')"
    [ -z "$FINAL" ] && exit 0
    
    VERBS='(start|begin|proceed|continue|create|implement|write|update|fix|add|run|check|investigate|work|make|set|move|look|open|draft|explore|apply|push|refactor|clean|test)'
    MATCH=""
    printf '%s' "$FINAL" | grep -qiE "(^|[^a-z])i('|’)?ll (now |then |next |also |go ahead and )?$VERBS" && MATCH="ill-promise"
    [ -z "$MATCH" ] && printf '%s' "$FINAL" | grep -qiE "(^|[^a-z])i will (now |then |next |also )?$VERBS" && MATCH="i-will"
    [ -z "$MATCH" ] && printf '%s' "$FINAL" | grep -qiE "(^|[[:space:]])next steps?:" && MATCH="next-steps"
    [ -z "$MATCH" ] && printf '%s' "$FINAL" | grep -qiE "let me know (if|when|whether|what|which|and)" && MATCH="let-me-know"
    [ -z "$MATCH" ] && printf '%s' "$FINAL" | grep -qiE "would you like me to" && MATCH="would-you-like"
    [ -z "$MATCH" ] && printf '%s' "$FINAL" | grep -qiE "(^|[^a-z])shall i " && MATCH="shall-i"
    # Golden calibration 2026-07-02: bare "want me to " blocked real Fable endings
    # (assess-only tasks ending "want me to apply the fix?" — a genuine decision
    # question). Anchor to continuation verbs so only in-scope deferral blocks.
    [ -z "$MATCH" ] && printf '%s' "$FINAL" | grep -qiE "(^|[^a-z])want me to (continue|proceed|keep going|finish|do the rest)" && MATCH="want-me-to"
    
    MODE="${1:-main}"
    
    # Opt-in LLM judge tier (main mode only, only when tier 1 found nothing).
    if [ -z "$MATCH" ] && [ "$MODE" = "main" ] && [ "${FABLE_STOP_JUDGE:-0}" = "1" ] \
       && command -v claude >/dev/null 2>&1; then
      # --bare requires API-key auth and breaks OAuth-only machines (Task 15 finding); inherit session auth instead — plugin contamination is acceptable for a 10-word verdict.
      VERDICT="$(printf 'Does this assistant turn-ending violate the rule "finish the work instead of promising it; do not seek permission for reversible in-scope actions"? Reply with exactly YES or NO.\n\n---\n%s' "$FINAL" \
        | claude -p --model "${FABLE_STOP_JUDGE_MODEL:-claude-haiku-4-5-20251001}" 2>/dev/null | tr -d '[:space:]')"
      [ "$VERDICT" = "YES" ] && MATCH="judge"
    fi
    
    [ -z "$MATCH" ] && exit 0
    
    if [ "$MODE" = "subagent" ]; then
      fable_telemetry "stop-gate-subagent" "$MATCH" "$SESSION"
      REASON="Fable subagent discipline: your final message is your return value. Return your findings now — conclusions with evidence, not intentions, plans, or offers."
    else
      fable_telemetry "stop-gate" "$MATCH" "$SESSION"
      REASON="Fable turn discipline: your last paragraph promises or proposes work instead of doing it. Do that work now — retry errors and gather missing information yourself. If you are genuinely blocked on something only the user can provide, state that blocking question plainly and stop."
    fi
    
    printf '{"decision": "block", "reason": "%s"}' "$REASON"
    exit 0
    

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.

@rennf93
Ships withfable-mode

Make Claude Opus 4.8 in Claude Code behave as much like Claude Fable 5 as possible. The doctrine was transcribed by Fable 5 itself; hooks enforce it at the harness level; an eval loop measures convergence against golden Fable transcripts.

343 skills1 agent
Get the whole plugin

Stars on GitHub

Momentum

stars in total
34
Star history of rennf93/opus-fable-playbook 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 fable-mode.

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