Skip to content
Development
Hook

Hooks

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

From plugin
encoding-guard
161 command5 hooks

Claude Code

> /plugin marketplace add ymonster/claude_encoding_guard
> /plugin install encoding-guard@claude-encoding-guard

What fires, and when

PreToolUse

  • MatchesRead|Edit|Writeuv run --script "${CLAUDE_PLUGIN_ROOT}/hooks/encoding_guard.py" pre

PostToolUse

  • MatchesEdit|Writeuv run --script "${CLAUDE_PLUGIN_ROOT}/hooks/encoding_guard.py" post

Stop

  • uv run --script "${CLAUDE_PLUGIN_ROOT}/hooks/encoding_guard.py" restore-all

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.

  • Matchesresumeuv run --script "${CLAUDE_PLUGIN_ROOT}/hooks/encoding_guard.py" restore-all

SessionEnd

  • uv run --script "${CLAUDE_PLUGIN_ROOT}/hooks/encoding_guard.py" restore-all
Read hooks/hooks.json

In the plugin's words

How encoding-guard describes its own hook set.

Encoding guard hooks - preserve non-UTF-8 file encodings when Claude Code edits

Where it lives

  • hooks/encoding_guard.pyRunsGitHub
    Read the script
    #!/usr/bin/env -S uv run --script
    # /// script
    # requires-python = ">=3.10"
    # dependencies = ["chardet>=5,<6", "binaryornot"]
    # ///
    """
    encoding_guard.py - Preserve file encoding and line endings when Claude Code edits files.
    
    Strategy:
        1. PreToolUse (Read):  detect encoding + line endings, convert to UTF-8 so Claude reads correctly
        2. PreToolUse (Edit):  file already UTF-8 (cache exists), skip
        3. PostToolUse (Edit): restore original encoding + line endings from session cache
        4. Stop / SessionEnd / SessionStart(resume) (restore-all):
           restore any remaining files — turn cleanup, exit backstop, and
           crash-residue healing before the resumed session acts
    
    Recovery commands (user-invoked CLI, not hooks):
        scan / scan-current           read-only report of cache records
        restore-current               restore orphaned files under the cwd
        prune-current                 delete obsolete records under the cwd
    
    Cache layout:
        <tempdir>/.cc_encoding_cache/<session_id>/<sha256(normalized_path)[:16]>.json
    
    Each session gets its own cache directory. Hooks operate strictly on their
    own session's records; only the user-invoked recovery commands may act on
    other sessions' records. Stale EMPTY session dirs (>24h) are removed on
    startup — record-bearing dirs are never auto-deleted.
    Stdin is read as binary (sys.stdin.buffer) to avoid Windows codepage issues.
    """
    
    # Read stdin immediately before any heavy imports (Windows stdin reliability).
    # Hook modes only: CC feeds hooks a pipe that closes. In the recovery CLI
    # modes stdin may be an interactive terminal, where a blind read() would
    # block forever.
    import sys
    _raw_stdin = b""
    if len(sys.argv) > 1 and sys.argv[1] in ("pre", "post", "restore-all"):
        _raw_stdin = sys.stdin.buffer.read()
    
    import contextlib
    import json
    import os
    import hashlib
    import tempfile
    import time
    
    CACHE_ROOT = os.path.join(tempfile.gettempdir(), ".cc_encoding_cache")
    STALE_HOURS = 24
    LOG_FILE = os.path.join(CACHE_ROOT, "encoding_guard.log")
    LOG_MAX_BYTES = 512 * 1024
    
    RESTORE_ENCODINGS = {
        "gb2312", "gbk", "gb18030",
        "big5", "big5hkscs",
        "euc-tw",
        "shift_jis", "euc-jp", "iso-2022-jp",
        "euc-kr",
        "windows-1252", "iso-8859-1",
        "windows-1251",
    }
    
    ENCODING_ALIASES = {
        "gb2312": "gbk",
        "iso-8859-1": "windows-1252",
    }
    
    # Encodings whose chardet detection (>=0.5 confidence + name-in-RESTORE) is
    # reliable enough to skip the binaryornot pre-check. binaryornot's decision
    # tree false-positives mixed-content files in two patterns:
    #   - short CJK files (<~500B Shift_JIS/EUC-JP/EUC-KR/Big5/GB18030)
    #   - mixed Cyrillic + ASCII files (e.g., source code with CP1251 comments)
    # For these encodings chardet's structural validators are strict enough on
    # their own; real binaries cannot reach the confidence threshold.
    STRUCTURAL_TRUSTED = {
        "gbk", "gb18030",
        "big5", "big5hkscs",
        "shiftjis", "eucjp", "euckr", "iso2022jp",
        "windows1251",
    }
    
    
    def _strip_enc(name: str) -> str:
        return name.lower().replace("-", "").replace("_", "")
    
    
    def _log(msg: str):
        sys.stderr.write(f"encoding_guard: {msg}\n")
    
    
    def _flog(event: str):
        """Append one line to the forensic log (CACHE_ROOT/encoding_guard.log).
    
        Hook stderr is captured into transcripts, but operations that run in
        process-shutdown windows (e.g. SessionEnd restore-all after the
        terminal is closed) never reach a transcript — this file is the
        durable trail for every file mutation. Best-effort: never raises.
        """
        try:
            os.makedirs(CACHE_ROOT, exist_ok=True)
            try:
                if os.path.getsize(LOG_FILE) > LOG_MAX_BYTES:
                    os.replace(LOG_FILE, LOG_FILE + ".1")
            except OSError:
                pass
            stamp = time.strftime("%Y-%m-%dT%H:%M:%S")
            with open(LOG_FILE, "a", encoding="utf-8") as f:
                f.write(f"{stamp} {event}\n")
        except OSError:
            pass
    
    
    def normalize_encoding(enc: str) -> str:
        """Map chardet output to a Python codec name.
    
        Apply ENCODING_ALIASES safety mappings (gb2312 -> gbk, iso-8859-1 ->
        windows-1252) when applicable; otherwise preserve chardet's original
        name verbatim. Returning the stripped form (e.g. "windows1251" without
        its dash) breaks codec lookup since Python's codec registry recognizes
        "windows-1251" / "cp1251" but NOT "windows1251".
        """
        stripped = _strip_enc(enc)
        for orig, alias in ENCODING_ALIASES.items():
            if stripped == _strip_enc(orig):
                return alias
        return enc
    
    
    def normalize_path(path: str) -> str:
        """Normalize path for consistent cache keys across pre/post and platforms."""
        return os.path.normcase(os.path.normpath(path))
    
    
    def file_hash(path: str) -> str:
        return hashlib.sha256(normalize_path(path).encode()).hexdigest()[:16]
    
    
    def sanitize_session_id(session_id: str) -> str:
        """Prevent path traversal from untrusted session_id."""
        return session_id.replace(os.sep, "_").replace("/", "_").replace("\\", "_")
    
    
    def session_dir(session_id: str) -> str:
        return os.path.join(CACHE_ROOT, sanitize_session_id(session_id))
    
    
    def cache_path(session_id: str, path: str) -> str:
        return os.path.join(session_dir(session_id), file_hash(path) + ".json")
    
    
    def cleanup_stale_sessions(current_session_id: str):
        """Remove EMPTY stale session dirs only. Never touches another session's records.
    
        An orphaned record is the only copy of "this file on disk should be
        re-encoded back" — deleting it is irreversible information loss, and
        restoring it automatically can override a user who has since accepted
        the UTF-8 state. Cross-session records are therefore acted on only by
        the user, via the scan/restore-current/prune-current commands.
        """
        if not os.path.exists(CACHE_ROOT):
            return
        current_dir_name = sanitize_session_id(current_session_id)
        now = time.time()
        try:
            for name in os.listdir(CACHE_ROOT):
                if name == current_dir_name:
         

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.

@ymonster
Ships withencoding-guard

Preserve non-UTF-8 file encodings and line endings when Claude Code edits your files.

Stars on GitHub

Momentum

stars in total
16
Star history of ymonster/claude_encoding_guard 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 encoding-guard.

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