Skip to content
Development
Hook

Hooks

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

From plugin
claude-token-reducer
461 skill4 agents1 command1 hook
+1
Install
> /plugin marketplace add Madhan230205/token-reducer
> /plugin install claude-token-reducer@Madhan230205-claude-token-reducer

Ships with claude-token-reducer. Installing the plugin gets these hooks.

What fires, and when

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.

  • python ${CLAUDE_PLUGIN_ROOT}/hooks/userprompt_guard.py
Read hooks/hooks.json

In the plugin's words

How claude-token-reducer describes its own hook set.

token-reducer prompt guardrails for bypass prevention and session hygiene reminders

Where it lives

  • hooks/userprompt_guard.pyRunsGitHub
    Read the script
    #!/usr/bin/env python3
    """User prompt guardrails for token-reducer.
    
    Goals:
    1) Warn when user appears to paste very large raw content (pipeline bypass risk).
    2) Periodically remind to compact/start fresh sessions to avoid context drift.
    """
    
    from __future__ import annotations
    
    import json
    import os
    import subprocess
    import sys
    from pathlib import Path
    from typing import Any
    
    
    MAX_PROMPT_WORDS = 900
    MAX_PROMPT_LINES = 120
    HARD_TRUNCATE_WORDS = 800
    HARD_BLOCK_WORDS = 3000
    REMINDER_TURNS_DEFAULT = {5, 8, 10, 12, 15, 20, 28, 36}
    AUTO_COMPACT_TURN_DEFAULT = 10
    AUTO_RESET_TURN_DEFAULT = 40
    CRITICAL_RESET_TURN_DEFAULT = 50
    
    
    def _load_guard_settings(plugin_root: str) -> dict[str, Any]:
        path = Path(plugin_root) / "settings.json"
        if not path.is_file():
            return {}
        try:
            data = json.loads(path.read_text(encoding="utf-8"))
        except (OSError, json.JSONDecodeError):
            return {}
        if not isinstance(data, dict):
            return {}
        pg = data.get("promptGuard")
        tr = data.get("tokenReducer")
        out: dict[str, Any] = {}
        if isinstance(pg, dict):
            out["promptGuard"] = pg
        if isinstance(tr, dict):
            out["tokenReducer"] = tr
        return out
    
    
    def _guard_params(plugin_root: str) -> tuple[int, int, int, set[int]]:
        blob = _load_guard_settings(plugin_root)
        pg = blob.get("promptGuard") or {}
        tr = blob.get("tokenReducer") or {}
    
        auto_compact = int(pg.get("autoCompactTurn", AUTO_COMPACT_TURN_DEFAULT))
        auto_reset = int(pg.get("autoResetTurn", AUTO_RESET_TURN_DEFAULT))
        critical = int(pg.get("criticalResetTurn", CRITICAL_RESET_TURN_DEFAULT))
    
        reminder: set[int] = set()
        if isinstance(pg.get("reminderTurns"), list):
            for x in pg["reminderTurns"]:
                try:
                    reminder.add(int(x))
                except (TypeError, ValueError):
                    pass
        hist = tr.get("historyCompactReminderTurns")
        if isinstance(hist, list):
            for x in hist:
                try:
                    reminder.add(int(x))
                except (TypeError, ValueError):
                    pass
        if not reminder:
            reminder = set(REMINDER_TURNS_DEFAULT)
    
        return auto_compact, auto_reset, critical, reminder
    
    
    def estimate_tokens(text: str) -> int:
        return max(1, int(len(text.split()) * 1.3))
    
    
    def extract_prompt(payload: Any) -> str:
        if isinstance(payload, dict):
            for key in ("user_prompt", "prompt", "message", "input"):
                value = payload.get(key)
                if isinstance(value, str):
                    return value
            for value in payload.values():
                extracted = extract_prompt(value)
                if extracted:
                    return extracted
            return ""
    
        if isinstance(payload, list):
            for item in payload:
                extracted = extract_prompt(item)
                if extracted:
                    return extracted
            return ""
    
        return payload if isinstance(payload, str) else ""
    
    
    def extract_session_id(payload: Any) -> str:
        if isinstance(payload, dict):
            for key in ("session_id", "sessionId", "conversation_id", "conversationId"):
                value = payload.get(key)
                if isinstance(value, str) and value.strip():
                    return value.strip()
            for value in payload.values():
                sid = extract_session_id(value)
                if sid:
                    return sid
        elif isinstance(payload, list):
            for item in payload:
                sid = extract_session_id(item)
                if sid:
                    return sid
        return "default"
    
    
    def load_state(path: Path) -> dict[str, Any]:
        if not path.exists():
            return {"sessions": {}}
        try:
            return json.loads(path.read_text(encoding="utf-8"))
        except Exception:
            return {"sessions": {}}
    
    
    def save_state(path: Path, state: dict[str, Any]) -> None:
        path.parent.mkdir(parents=True, exist_ok=True)
        path.write_text(json.dumps(state, ensure_ascii=False), encoding="utf-8")
    
    
    def main() -> int:
        try:
            payload = json.load(sys.stdin)
        except Exception:
            print(json.dumps({}), file=sys.stdout)
            return 0
    
        prompt = extract_prompt(payload)
        session_id = extract_session_id(payload)
    
        plugin_root = os.environ.get("CLAUDE_PLUGIN_ROOT", "")
        state_path = Path(plugin_root) / ".cache" / "token-reducer" / "prompt_guard_state.json"
        state = load_state(state_path)
        sessions = state.setdefault("sessions", {})
        turns = int(sessions.get(session_id, 0)) + 1
        sessions[session_id] = turns
        state["sessions"] = sessions
        try:
            save_state(state_path, state)
        except Exception:
            pass
    
        auto_compact_turn, auto_reset_turn, critical_reset_turn, reminder_turns = _guard_params(
            plugin_root
        )
    
        messages: list[str] = []
        result: dict[str, Any] = {}
    
        if prompt:
            word_count = len(prompt.split())
            line_count = prompt.count("\n") + 1
            has_compact_packet = "CONTEXT_PACKET_START" in prompt
            has_token_reducer_intent = "/token-reducer" in prompt or "token-reducer" in prompt.lower()
            bypass = has_compact_packet or has_token_reducer_intent
    
            if not bypass and word_count > HARD_BLOCK_WORDS:
                # Hard block: reject the prompt entirely
                messages.append(
                    f"๐Ÿšซ Prompt BLOCKED: {word_count} words (~{estimate_tokens(prompt)} tokens) exceeds the "
                    f"{HARD_BLOCK_WORDS}-word hard limit. Reduce your prompt size or pass large content "
                    "via --inputs and run /token-reducer instead. Prompt was not submitted."
                )
                result["rejectInput"] = True
                result["systemMessage"] = "\n\n".join(messages)
                print(json.dumps(result), file=sys.stdout)
                return 0
    
            if not bypass and word_count > HARD_TRUNCATE_WORDS:
                # TPCH: Zero-Turn Auto-Compression โ€” intercept before LLM sees the prompt
                messages.append(
                    f"โšก Auto-Compression Engaged: Int

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 withclaude-token-reducer

โšก Cut Claude token usage by 90%+ โ€” free, open-source, local-first context compression for Claude Code. Hybrid RAG (BM25 + ONNX vectors), AST chunking, reranking. No API needed.

Get the whole plugin