Hooks
What octo runs automatically, and when. A hook is a command Claude Code fires at a fixed moment, without you asking for it.
> /plugin marketplace add nyldn/claude-octopusShips with octo. Installing the plugin gets these hooks.
What fires, and when
PreToolUse
- Matches
Bash${CLAUDE_PLUGIN_ROOT}/hooks/provider-routing-validator.sh - Matches
TaskCreate${CLAUDE_PLUGIN_ROOT}/hooks/task-dependency-validator.sh - Matches
Bash${CLAUDE_PLUGIN_ROOT}/hooks/codex-exec-guard.sh${CLAUDE_PLUGIN_ROOT}/hooks/codex-exec-guard.sh${CLAUDE_PLUGIN_ROOT}/hooks/codex-exec-guard.sh - Matches
Bash${CLAUDE_PLUGIN_ROOT}/hooks/scheduler-security-gate.sh - Matches
EnterPlanMode${CLAUDE_PLUGIN_ROOT}/hooks/plan-mode-interceptor.sh - Matches
Bash${CLAUDE_PLUGIN_ROOT}/hooks/careful-check.sh - Matches
Edit|Write${CLAUDE_PLUGIN_ROOT}/hooks/freeze-check.sh
PostToolUse
- Matches
Bash|Agent|Write|Edit|Read|WebFetch|Grep${CLAUDE_PLUGIN_ROOT}/hooks/post-tool-dispatch.sh - Matches
Bash${CLAUDE_PLUGIN_ROOT}/hooks/quality-gate.sh - Matches
TaskUpdate${CLAUDE_PLUGIN_ROOT}/hooks/task-completion-checkpoint.sh - Matches
Bash${CLAUDE_PLUGIN_ROOT}/hooks/telemetry-webhook.sh
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.
${CLAUDE_PLUGIN_ROOT}/scripts/helpers/ensure-plugin-root.sh${CLAUDE_PLUGIN_ROOT}/hooks/context-reinforcement.sh${CLAUDE_PLUGIN_ROOT}/hooks/auto-router-inject.sh${CLAUDE_PLUGIN_ROOT}/hooks/session-start-memory.sh${CLAUDE_PLUGIN_ROOT}/hooks/statusline-auto-repair.sh${CLAUDE_PLUGIN_ROOT}/hooks/version-advisory.sh${CLAUDE_PLUGIN_ROOT}/hooks/plugin-update-advisory.sh${CLAUDE_PLUGIN_ROOT}/hooks/fable5-inject.sh${CLAUDE_PLUGIN_ROOT}/hooks/discipline-inject.sh
TeammateIdle
${CLAUDE_PLUGIN_ROOT}/hooks/teammate-idle-dispatch.sh
TaskCompleted
${CLAUDE_PLUGIN_ROOT}/hooks/task-completed-transition.sh
ConfigChange
${CLAUDE_PLUGIN_ROOT}/hooks/config-change-handler.sh
SubagentStop
${CLAUDE_PLUGIN_ROOT}/hooks/subagent-result-capture.sh${CLAUDE_PLUGIN_ROOT}/hooks/subagent-stop-gate.sh
InstructionsLoaded
${CLAUDE_PLUGIN_ROOT}/hooks/instructions-loaded.sh
PreCompact
${CLAUDE_PLUGIN_ROOT}/hooks/pre-compact.sh
SessionEnd
${CLAUDE_PLUGIN_ROOT}/hooks/session-end.sh${CLAUDE_PLUGIN_ROOT}/hooks/workflow-verification.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/user-prompt-submit.sh${CLAUDE_PLUGIN_ROOT}/hooks/done-criteria.sh${CLAUDE_PLUGIN_ROOT}/hooks/github-work-queue-watch.sh
StopFailure
${CLAUDE_PLUGIN_ROOT}/hooks/stop-failure-log.sh
CwdChanged
${CLAUDE_PLUGIN_ROOT}/hooks/cwd-changed.sh
TaskCreated
${CLAUDE_PLUGIN_ROOT}/hooks/discipline-task-gate.sh${CLAUDE_PLUGIN_ROOT}/hooks/task-dependency-validator.sh
PostCompact
${CLAUDE_PLUGIN_ROOT}/hooks/post-compact.sh
Elicitation
${CLAUDE_PLUGIN_ROOT}/hooks/elicitation-handler.sh
ElicitationResult
${CLAUDE_PLUGIN_ROOT}/hooks/elicitation-handler.sh result
PermissionDenied
${CLAUDE_PLUGIN_ROOT}/hooks/permission-denied-log.sh
Where it lives
- hooks/agent-teams-phase-gate.shGitHub
- hooks/architecture-gate.shGitHub
- hooks/auto-router-inject.shRunsGitHub
Read the script
#!/usr/bin/env bash # auto-router-inject.sh - Compact SessionStart routing contract. # # Inject one small, explicit contract early in the session so hook-provided # routing instructions are honored. set -euo pipefail _octo_hook_exit() { local c=$?; if [[ $c -ne 0 ]]; then echo "[hook:$(basename "$0")] exit $c" >&2 2>/dev/null || true; fi; return 0; } trap _octo_hook_exit EXIT # Provider subprocesses are already inside an Octopus dispatch. Do not inject # a second routing contract that can trigger recursive orchestration. [[ "${OCTOPUS_PROVIDER_CHILD:-false}" == "true" ]] && exit 0 escape_for_json() { local s="$1" s="${s//\\/\\\\}" s="${s//\"/\\\"}" s="${s//$'\n'/\\n}" s="${s//$'\r'/\\r}" s="${s//$'\t'/\\t}" printf '%s' "$s" } emit_session_context() { local context="$1" local escaped escaped=$(escape_for_json "$context") if [[ -n "${CURSOR_PLUGIN_ROOT:-}" ]]; then printf '{"additional_context":"%s"}\n' "$escaped" elif [[ -n "${CLAUDE_PLUGIN_ROOT:-}" && -z "${COPILOT_CLI:-}" ]]; then printf '{"hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":"%s"}}\n' "$escaped" else printf '{"additionalContext":"%s"}\n' "$escaped" fi } normalize_router_mode() { local raw raw=$(printf '%s' "${1:-}" | tr '[:upper:]' '[:lower:]' | tr '_' '-') case "$raw" in off|disabled|disable|none|0) echo "off" ;; suggest|suggestion|advisory|hint|hints|false|no) echo "suggest" ;; invoke|auto|auto-invoke|autoinvoke|mandatory|true|yes|on|1) echo "invoke" ;; *) return 1 ;; esac } json_pref_value() { local file="$1" local key="$2" [[ -f "$file" ]] || return 1 command -v python3 &>/dev/null || return 1 python3 -c " import json, sys try: with open(sys.argv[1]) as f: data = json.load(f) value = data.get(sys.argv[2], None) if value is not None: print(str(value)) except Exception: pass " "$file" "$key" 2>/dev/null } # Explicit-only is the safe default. Preferences and environment overrides can # still opt into suggest/invoke behavior. AUTO_ROUTER_MODE="off" PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(cd "$(dirname "$0")/.." && pwd)}" SETTINGS_FILE="${PLUGIN_ROOT}/settings.json" [[ -f "$SETTINGS_FILE" ]] || SETTINGS_FILE="${PLUGIN_ROOT}/.claude-plugin/settings.json" if [[ -f "$SETTINGS_FILE" ]]; then _setting_router=$(json_pref_value "$SETTINGS_FILE" "OCTOPUS_AUTO_ROUTER_MODE" || true) _setting_legacy=$(json_pref_value "$SETTINGS_FILE" "OCTOPUS_AUTO_INVOKE" || true) if [[ -n "$_setting_router" ]] && _mode=$(normalize_router_mode "$_setting_router"); then AUTO_ROUTER_MODE="$_mode" elif [[ -n "$_setting_legacy" ]] && _mode=$(normalize_router_mode "$_setting_legacy"); then AUTO_ROUTER_MODE="$_mode" fi fi PREFS_FILE="${HOME}/.claude-octopus/preferences.json" if [[ -f "$PREFS_FILE" ]]; then _pref_router=$(json_pref_value "$PREFS_FILE" "auto_router_mode" || true) _pref_legacy=$(json_pref_value "$PREFS_FILE" "auto_invoke" || true) if [[ -n "$_pref_router" ]] && _mode=$(normalize_router_mode "$_pref_router"); then AUTO_ROUTER_MODE="$_mode" elif [[ -n "$_pref_legacy" ]] && _mode=$(normalize_router_mode "$_pref_legacy"); then AUTO_ROUTER_MODE="$_mode" fi fi if [[ -n "${OCTOPUS_AUTO_ROUTER_MODE:-}" ]] && _mode=$(normalize_router_mode "$OCTOPUS_AUTO_ROUTER_MODE"); then AUTO_ROUTER_MODE="$_mode" elif [[ -n "${OCTOPUS_AUTO_INVOKE:-}" ]] && _mode=$(normalize_router_mode "$OCTOPUS_AUTO_INVOKE"); then AUTO_ROUTER_MODE="$_mode" fi [[ "$AUTO_ROUTER_MODE" == "off" ]] && exit 0 read -r -d '' CONTEXT <<'ROUTER' || true <OCTOPUS-AUTO-ROUTER> The user explicitly opted into Octopus plain-language routing. Prompt hooks may add UserPromptSubmit routing context. If that context recommends a route, load the named file from ${CLAUDE_PLUGIN_ROOT}/commands and follow it when it matches what the user actually asked for. Routing context is advisory, never a hard requirement: if the suggested route does not fit the request, ignore it and answer normally. Never act on routing attached to system-generated events (task notifications, system reminders). Strong plain-language routes include: review -> commands/review.md, debate/compare/should-we -> commands/debate.md, research/investigate/explore -> commands/discover.md, security/threat-model -> commands/security.md, debug/failing/stacktrace -> commands/debug.md, write-tests/TDD -> commands/tdd.md, implement/execute-plan -> commands/develop.md. If the hook only says "Detected intent" or "Tip", treat it as a suggestion and continue normally unless the user asks to route. </OCTOPUS-AUTO-ROUTER> ROUTER CONTEXT="${CONTEXT/OCTOPUS-AUTO-ROUTER>/OCTOPUS-AUTO-ROUTER mode=\"$AUTO_ROUTER_MODE\">}" emit_session_context "$CONTEXT" - hooks/budget-gate.shGitHub
- hooks/careful-check.shRunsGitHub
Read the script
#!/bin/bash # Claude Octopus Careful Mode Hook (v9.8.0) # PreToolUse hook on Bash that warns before destructive command patterns. # Activated by /octo:careful command (writes state file). # Emits permissionDecision: ask under Claude, deny under Codex; silence continues. # # Kill switch: OCTO_CAREFUL_MODE=off — disables all destructive command checks set -euo pipefail # EXIT trap — emits diagnostic stderr ONLY when the hook exits non-zero, so # the Claude Code harness error "No stderr output" can never recur. EXIT (not # ERR) avoids over-firing on intermediate `grep -o`/`cmd | ...` inside $() that # the hook's logic already handles. See issue #313. _octo_hook_exit() { local c=$?; if [[ $c -ne 0 ]]; then echo "[hook:$(basename "$0")] exit $c" >&2 2>/dev/null || true; fi; return 0; } trap _octo_hook_exit EXIT _HOOK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "$_HOOK_DIR/../scripts/lib/session-id.sh" 2>/dev/null || true # Kill switch — respect user's choice to disable careful mode entirely # (careful mode is opt-in via /octo:careful; OCTO_CAREFUL_MODE=off is the dedicated off-switch) [[ "${OCTO_CAREFUL_MODE:-on}" == "off" ]] && exit 0 # Read tool input from stdin if command -v timeout &>/dev/null; then INPUT=$(timeout 3 cat 2>/dev/null || true) else INPUT=$(cat 2>/dev/null || true) fi [[ -z "$INPUT" ]] && INPUT='{}' # Check if careful mode is active if declare -f octo_session_state_file >/dev/null 2>&1; then STATE_FILE=$(octo_session_state_file "careful" "txt" "$INPUT") else STATE_FILE="/tmp/octopus-careful-${CLAUDE_CODE_SESSION_ID:-${CLAUDE_SESSION_ID:-$$}}.txt" fi if [[ ! -f "$STATE_FILE" ]]; then : # pass-through — current hook schema treats silence as continue exit 0 fi _octo_invalid_input() { printf '%s\n' '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"Careful mode could not validate the tool input. Check that Python 3 is installed and the hook input is valid JSON before retrying."}}' exit 0 } _octo_careful_decision() { python3 "$_HOOK_DIR/safety-contract.py" careful-decision "$1" 2>/dev/null || _octo_invalid_input } # Codex calls exec_command Bash in PreToolUse, just as Claude does. # Parse JSON structurally so whitespace and escaped quotes retain their meaning. TOOL_NAME=$(printf '%s' "$INPUT" | python3 "$_HOOK_DIR/safety-contract.py" field tool_name 2>/dev/null) || _octo_invalid_input [[ "$TOOL_NAME" == "Bash" ]] || exit 0 COMMAND=$(printf '%s' "$INPUT" | python3 "$_HOOK_DIR/safety-contract.py" field command 2>/dev/null) || _octo_invalid_input CHECK_TEXT="$COMMAND" [[ -n "$CHECK_TEXT" ]] || exit 0 # ── Destructive pattern checks ──────────────────────────────────────── # 1. rm -rf — but allow safe exceptions (node_modules, dist, .next, __pycache__, build, coverage, .turbo) if echo "$CHECK_TEXT" | grep -qE '(^|[^[:alnum:]_])rm\s+(-[a-zA-Z]*r[a-zA-Z]*f|-r\s+-f|-f\s+-r|--recursive\s+--force)'; then # Check if the target is a safe exception safe=false for safe_dir in node_modules dist .next __pycache__ build coverage .turbo; do if echo "$CHECK_TEXT" | grep -qE "rm\s+.*${safe_dir}(\s|$|/)"; then safe=true break fi done if [[ "$safe" == "false" ]]; then _octo_careful_decision 'Destructive command detected: rm -rf. This recursively force-deletes files.' exit 0 fi fi # 2. SQL destructive operations. A SQL-looking string alone is not execution: source # searches and output commands routinely contain examples such as `DROP TABLE` and # `TRUNCATE users`. Gate only when the statement is either a direct shell command or # appears alongside a known SQL client. This keeps read-only grep/rg/printf commands # quiet while retaining coverage for client flags, stdin pipes, and heredocs. _octo_drop_pat='DROP\s+(TABLE|DATABASE)(\s+IF\s+EXISTS)?(\s+["'\''`]?[[:alnum:]_.$-]+["'\''`]?)?' _octo_truncate_pat='TRUNCATE\s+["'\''`]?[[:alnum:]_.$-]+["'\''`]?(\s+["'\''`]?[[:alnum:]_.$-]+["'\''`]?)?' _octo_sql_pat="${_octo_drop_pat}|${_octo_truncate_pat}" _octo_sql_client_pat='(^|[;&|][[:space:]]*)((sudo|command|env)[[:space:]]+)?([[:alpha:]_][[:alnum:]_]*=[^[:space:]]+[[:space:]]+)*(psql|mysql|mariadb|sqlite3|sqlcmd|cockroach\s+sql)([[:space:]]|$)' _octo_direct_sql_pat="^[[:space:]]*(${_octo_sql_pat})" # ERE alternation alone cannot distinguish `TRUNCATE TABLE foo` from the # incomplete `TRUNCATE TABLE`: the optional TABLE branch can backtrack and # consume TABLE as the identifier. Inspect candidate tokens so TABLE requires a # third token, while `TRUNCATE users` and quoted identifiers remain protected. _octo_has_destructive_sql() { local drop_matches truncate_matches drop_matches=$(echo "$CHECK_TEXT" | grep -oiE "$_octo_drop_pat" || true) if [[ -n "$drop_matches" ]] && printf '%s\n' "$drop_matches" | awk ' { if (NF < 3) next if (tolower($3) == "if") { if (NF < 5 || tolower($4) != "exists") next target = $5 } else { target = $3 } quote = substr(target, 1, 1) apostrophe = sprintf("%c", 39) if ((quote == "\"" || quote == "`" || quote == apostrophe) && substr(target, length(target), 1) != quote) next found = 1 exit } END { exit(found ? 0 : 1) } '; then return 0 fi truncate_matches=$(echo "$CHECK_TEXT" | grep -oiE "$_octo_truncate_pat" || true) [[ -n "$truncate_matches" ]] || return 1 printf '%s\n' "$truncate_matches" | awk ' { raw = $2 token = tolower(raw) gsub(/^["`]/, "", token) gsub(/["`;]$/, "", token) apostrophe = sprintf("%c", 39) first = substr(raw, 1, 1) quoted = (first == "\"" || first == "`" || first == apostrophe) if (token == "table" && !quoted && NF < 3) next t - hooks/code-quality-gate.shGitHub
- hooks/codex-exec-guard.shRunsGitHub
Read the script
#!/bin/bash # Provider CLI guard — blocks unsafe direct non-interactive provider dispatch. # PreToolUse hook on Bash. Returns block decision with correction message. # WHY: `codex "prompt"` launches interactive TUI which fails in non-TTY (Claude Code Bash tool). # `codex exec "prompt"` is the correct non-interactive mode. # Direct Qwen dispatch can enter OAuth device authorization and open a browser. # Direct Gemini dispatch uses a retired individual client. Both bypass Octopus # provider admission and authentication checks. set -euo pipefail # EXIT trap — emits diagnostic stderr ONLY when the hook exits non-zero, so # the Claude Code harness error "No stderr output" can never recur. EXIT (not # ERR) avoids over-firing on intermediate `grep -o`/`cmd | ...` inside $() that # the hook's logic already handles. See issue #313. _octo_hook_exit() { local c=$?; if [[ $c -ne 0 ]]; then echo "[hook:$(basename "$0")] exit $c" >&2 2>/dev/null || true; fi; return 0; } trap _octo_hook_exit EXIT # Note: this gate guards correctness, not user permission policy, so it runs # regardless of bypassPermissions. INPUT=$(cat 2>/dev/null || true) [[ -z "$INPUT" ]] && exit 0 # Extract command if command -v jq &>/dev/null; then COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // ""' 2>/dev/null || echo "") else COMMAND=$(echo "$INPUT" | grep -o '"command":"[^"]*"' | head -1 | cut -d'"' -f4) fi [[ -z "$COMMAND" ]] && exit 0 # Find the closing parenthesis for a $(...) command substitution. Parentheses # inside quotes/backticks do not close the substitution; nested substitutions # and grouping parentheses increase its depth. Bash 3.2 compatible. _octo_command_sub_end() { local source="$1" start="$2" state="plain" escaped="false" depth=1 local i char next for ((i = start; i < ${#source}; i++)); do char="${source:i:1}" next="${source:i+1:1}" if [[ "$escaped" == "true" ]]; then escaped="false" continue fi case "$state" in single) [[ "$char" == "'" ]] && state="plain" ;; double) case "$char" in '"') state="plain" ;; "\\") escaped="true" ;; '$') if [[ "$next" == '(' ]]; then depth=$((depth + 1)) i=$((i + 1)) fi ;; esac ;; backtick) case "$char" in '`') state="plain" ;; "\\") escaped="true" ;; esac ;; plain) case "$char" in "'") state="single" ;; '"') state="double" ;; '`') state="backtick" ;; "\\") escaped="true" ;; '(') depth=$((depth + 1)) ;; ')') depth=$((depth - 1)) if [[ $depth -eq 0 ]]; then printf '%s\n' "$i" return 0 fi ;; esac ;; esac done return 1 } _octo_backtick_end() { local source="$1" start="$2" escaped="false" i char for ((i = start; i < ${#source}; i++)); do char="${source:i:1}" if [[ "$escaped" == "true" ]]; then escaped="false" elif [[ "$char" == "\\" ]]; then escaped="true" elif [[ "$char" == '`' ]]; then printf '%s\n' "$i" return 0 fi done return 1 } # Print the delimiter, tab-stripping flag, and quoting flag for the first # heredoc operator that appears in executable shell syntax on a line. Operators # inside quotes and comments are data, not syntax. _octo_heredoc_spec() { local line="$1" state="plain" escaped="false" local i char next previous j strip_tabs="false" quote delimiter="" quoted="false" for ((i = 0; i < ${#line}; i++)); do char="${line:i:1}" next="${line:i+1:1}" previous="${line:i-1:1}" if [[ "$escaped" == "true" ]]; then escaped="false" continue fi case "$state" in single) [[ "$char" == "'" ]] && state="plain" ;; double) case "$char" in '"') state="plain" ;; "\\") escaped="true" ;; esac ;; plain) case "$char" in "'") state="single" ;; '"') state="double" ;; "\\") escaped="true" ;; '#') if [[ $i -eq 0 || "$previous" == ' ' || "$previous" == $'\t' ]]; then return 1 fi ;; '<') [[ "$next" == '<' ]] || continue j=$((i + 2)) if [[ "${line:j:1}" == '-' ]]; then strip_tabs="true" j=$((j + 1)) fi while [[ "${line:j:1}" == ' ' || "${line:j:1}" == $'\t' ]]; do j=$((j + 1)) done quote="${line:j:1}" if [[ "$quote" == "'" || "$quote" == '"' ]]; then quoted="true" j=$((j + 1)) while [[ $j -lt ${#line} && "${line:j:1}" != "$quote" ]]; do delimiter="${delimiter}${line:j:1}" j=$((j + 1)) done [[ $j -lt ${#line} ]] || return 1 - hooks/config-change-handler.shRunsGitHub
Read the script
#!/usr/bin/env bash # Claude Octopus ConfigChange Hook Handler # Triggered when Claude Code configuration changes (v2.1.49+) # Detects Octopus setting changes and writes reload signal for orchestrate.sh # v8.29.0: Expanded from fast-mode-only to full Octopus settings hot-reload set -euo pipefail # EXIT trap — emits diagnostic stderr ONLY when the hook exits non-zero, so # the Claude Code harness error "No stderr output" can never recur. EXIT (not # ERR) avoids over-firing on intermediate `grep -o`/`cmd | ...` inside $() that # the hook's logic already handles. See issue #313. _octo_hook_exit() { local c=$?; if [[ $c -ne 0 ]]; then echo "[hook:$(basename "$0")] exit $c" >&2 2>/dev/null || true; fi; return 0; } trap _octo_hook_exit EXIT CONFIG_CHANGE_DATA="" if [[ ! -t 0 ]]; then CONFIG_CHANGE_DATA="$(cat)" fi SESSION_ID="${CLAUDE_SESSION_ID:-}" WORKFLOW_PHASE="${OCTOPUS_WORKFLOW_PHASE:-unknown}" SIGNAL_DIR="${HOME}/.claude-octopus" # Log the change for debugging if [[ "${VERBOSE:-false}" == "true" ]]; then echo "[ConfigChange] Session: $SESSION_ID, Phase: $WORKFLOW_PHASE" >&2 if [[ -n "$CONFIG_CHANGE_DATA" ]]; then echo "[ConfigChange] Data: $CONFIG_CHANGE_DATA" >&2 fi fi if [[ -n "$CONFIG_CHANGE_DATA" ]]; then # Detect fast mode toggle if echo "$CONFIG_CHANGE_DATA" | grep -q '"fast"' 2>/dev/null; then if [[ "${VERBOSE:-false}" == "true" ]]; then echo "[ConfigChange] Fast mode setting changed" >&2 fi fi # Detect Octopus-specific setting changes NEEDS_RELOAD=false for setting in OCTOPUS_ROUTING_MODE OCTOPUS_AUTONOMY OCTOPUS_OPUS_MODE \ OCTOPUS_MAX_COST_USD OCTOPUS_MAX_PARALLEL_AGENTS \ OCTOPUS_QUALITY_GATE_THRESHOLD OCTOPUS_WORKTREE_ISOLATION \ OCTOPUS_WEBHOOK_URL OCTOPUS_AGY_SANDBOX OCTOPUS_CODEX_SANDBOX \ OCTOPUS_MEMORY_INJECTION OCTOPUS_PERSONA_PACKS OCTOPUS_COST_WARNINGS \ OCTOPUS_TOOL_POLICIES; do if echo "$CONFIG_CHANGE_DATA" | grep -q "\"$setting\"" 2>/dev/null; then NEEDS_RELOAD=true if [[ "${VERBOSE:-false}" == "true" ]]; then echo "[ConfigChange] Octopus setting changed: $setting" >&2 fi fi done # Write reload signal for orchestrate.sh to pick up on next invocation if [[ "$NEEDS_RELOAD" == "true" ]]; then mkdir -p "$SIGNAL_DIR" echo "$(date -u +%Y-%m-%dT%H:%M:%SZ)" > "$SIGNAL_DIR/.config-reload-signal" 2>/dev/null || true if [[ "${VERBOSE:-false}" == "true" ]]; then echo "[ConfigChange] Wrote reload signal for orchestrate.sh" >&2 fi fi fi : # pass-through — current hook schema treats silence as continue exit 0 - hooks/context-awareness.shGitHub
- hooks/context-reinforcement.shRunsGitHub
Read the script
#!/bin/bash # Context Reinforcement Hook — SessionStart # Re-injects Iron Laws after context compaction so enforcement rules survive # conversation compression. Inspired by obra/superpowers v4.3.1 SessionStart pattern. # # Hook type: SessionStart # Returns: {"hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":"<CONTEXT-REINFORCEMENT>...</CONTEXT-REINFORCEMENT>"} set -euo pipefail # EXIT trap — emits diagnostic stderr ONLY when the hook exits non-zero, so # the Claude Code harness error "No stderr output" can never recur. EXIT (not # ERR) avoids over-firing on intermediate `grep -o`/`cmd | ...` inside $() that # the hook's logic already handles. See issue #313. _octo_hook_exit() { local c=$?; if [[ $c -ne 0 ]]; then echo "[hook:$(basename "$0")] exit $c" >&2 2>/dev/null || true; fi; return 0; } trap _octo_hook_exit EXIT # Read JSON payload from stdin (required by hook protocol) if command -v timeout &>/dev/null; then INPUT=$(timeout 3 cat 2>/dev/null || true) else INPUT=$(cat 2>/dev/null || true) fi [[ -z "$INPUT" ]] && INPUT='{}' ACTIVATION_LIB="${CLAUDE_PLUGIN_ROOT:-$(cd "$(dirname "$0")/.." && pwd)}/scripts/lib/hook-activation.sh" [[ -r "$ACTIVATION_LIB" ]] || exit 0 # shellcheck source=../scripts/lib/hook-activation.sh source "$ACTIVATION_LIB" 2>/dev/null || exit 0 octo_hook_profile_allows "context-reinforcement" || exit 0 octo_hook_workflow_active "$INPUT" || exit 0 # Build compact enforcement context only for the active, matching workflow. # Native disable-model-invocation frontmatter is the hard activation gate; this # reminder only helps an explicitly started workflow survive compaction. read -r -d '' CONTEXT <<'RULES' || true <CONTEXT-REINFORCEMENT source="🐙 Octopus"> Hard gates: no-stubs (verify before claiming done), test-first (failing test before code), debug-protocol (root cause before fix), orchestrate-only (use orchestrate.sh for research), factory-pipeline (no skipping steps). All Octopus commands and skills are explicit-only. Continue only the active workflow recorded for this Claude session. </CONTEXT-REINFORCEMENT> RULES # Escape the context for JSON output ESCAPED_CONTEXT=$(echo "$CONTEXT" | python3 -c "import sys,json; print(json.dumps(sys.stdin.read()))" 2>/dev/null | sed 's/^"//;s/"$//') # Return the hook response cat <<EOF {"hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":"${ESCAPED_CONTEXT}"}} EOF - hooks/cwd-changed.shRunsGitHub
Read the script
#!/usr/bin/env bash # cwd-changed.sh — Re-detect project context when working directory changes # Hook event: CwdChanged (CC v2.1.83+) # Outputs additionalContext with project type detection for the new directory. set -euo pipefail # EXIT trap — emits diagnostic stderr ONLY when the hook exits non-zero, so # the Claude Code harness error "No stderr output" can never recur. EXIT (not # ERR) avoids over-firing on intermediate `grep -o`/`cmd | ...` inside $() that # the hook's logic already handles. See issue #313. _octo_hook_exit() { local c=$?; if [[ $c -ne 0 ]]; then echo "[hook:$(basename "$0")] exit $c" >&2 2>/dev/null || true; fi; return 0; } trap _octo_hook_exit EXIT # Read hook input from stdin (JSON with new_cwd field) INPUT=$(cat 2>/dev/null) || INPUT="" [[ -z "$INPUT" ]] && exit 0 # Extract new CWD — try jq first, fall back to regex if command -v jq &>/dev/null; then NEW_CWD=$(echo "$INPUT" | jq -r '.new_cwd // empty' 2>/dev/null) || NEW_CWD="" else # Fallback: extract with grep NEW_CWD=$(echo "$INPUT" | grep -o '"new_cwd"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*"new_cwd"[[:space:]]*:[[:space:]]*"//' | sed 's/"$//' 2>/dev/null) || NEW_CWD="" fi [[ -z "$NEW_CWD" || ! -d "$NEW_CWD" ]] && exit 0 # Detect project characteristics context_hints="" # Check if it's a git repo if [[ -d "$NEW_CWD/.git" ]]; then context_hints="git-repo" fi # Detect language/framework if [[ -f "$NEW_CWD/package.json" ]]; then context_hints="${context_hints:+$context_hints, }node/js" if grep -q '"next"' "$NEW_CWD/package.json" 2>/dev/null; then context_hints="$context_hints, nextjs" fi if grep -q '"react"' "$NEW_CWD/package.json" 2>/dev/null; then context_hints="$context_hints, react" fi elif [[ -f "$NEW_CWD/pyproject.toml" || -f "$NEW_CWD/setup.py" || -f "$NEW_CWD/requirements.txt" ]]; then context_hints="${context_hints:+$context_hints, }python" elif [[ -f "$NEW_CWD/go.mod" ]]; then context_hints="${context_hints:+$context_hints, }go" elif [[ -f "$NEW_CWD/Cargo.toml" ]]; then context_hints="${context_hints:+$context_hints, }rust" fi # Detect if it has Claude config if [[ -d "$NEW_CWD/.claude" ]]; then context_hints="${context_hints:+$context_hints, }claude-configured" fi # Output context hint if we detected anything useful if [[ -n "$context_hints" ]]; then echo "[octopus] Directory changed to: $NEW_CWD (detected: $context_hints)" fi exit 0 - hooks/discipline-inject.shRunsGitHub
- hooks/discipline-task-gate.shRunsGitHub
- hooks/done-criteria.shRunsGitHub
- hooks/elicitation-handler.shRunsGitHub
- hooks/fable5-inject.shRunsGitHub
- hooks/freeze-check.shRunsGitHub
- hooks/frontend-gate.shGitHub
- hooks/github-work-queue-watch.shRunsGitHub
- hooks/instructions-loaded.shRunsGitHub
- hooks/octopus-hud.mjsGitHub
- hooks/octopus-statusline.shGitHub
- hooks/output-compressor.shGitHub
- hooks/perf-gate.shGitHub
- hooks/permission-denied-log.shRunsGitHub
- hooks/plan-mode-interceptor.shRunsGitHub
- hooks/plugin-update-advisory.shRunsGitHub
- hooks/post-compact.shRunsGitHub
- hooks/post-tool-dispatch.shRunsGitHub
- hooks/pre-compact.shRunsGitHub
- hooks/provider-routing-validator.shRunsGitHub
- hooks/quality-gate.shRunsGitHub
- hooks/safety-contract.pyGitHub
- hooks/scheduler-security-gate.shRunsGitHub
- hooks/security-gate.shGitHub
- hooks/session-end.shRunsGitHub
- hooks/session-start-memory.shRunsGitHub
- hooks/statusline-auto-repair.shRunsGitHub
- hooks/statusline-resolver.shGitHub
- hooks/stop-failure-log.shRunsGitHub
- hooks/strategy-rotation.shGitHub
- hooks/subagent-result-capture.shRunsGitHub
- hooks/subagent-stop-gate.shRunsGitHub
- hooks/task-completed-transition.shRunsGitHub
- hooks/task-completion-checkpoint.shRunsGitHub
- hooks/task-dependency-validator.shRunsGitHub
- hooks/teammate-idle-dispatch.shRunsGitHub
- hooks/telemetry-webhook.shRunsGitHub
- hooks/user-prompt-submit.shRunsGitHub
- hooks/version-advisory.shRunsGitHub
- hooks/workflow-verification.shRunsGitHub
All 51 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.
Every AI model has blind spots. Claude Octopus supports twelve external provider integrations — Codex, Antigravity CLI, Copilot, Qwen, Ollama, Perplexity, OpenRouter, OrcaRouter, OpenCode, Cursor CLI, Grok, and Kimi Code — alongside the built-in Claude Code
Repo: nyldn/claude-octopus

