Skip to content
Agent Memory
Hook

Hooks

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

From plugin
no-no-debug
1511 skill3 hooks
Install
$ npx -y skills add summerliuuu/no-no-debug --agent claude-code

Ships with no-no-debug. Installing the plugin gets these hooks.

Where it lives

  • hooks/post_tool_failure.shGitHub
    Read the script
    #!/usr/bin/env bash
    # PostToolUseFailure hook — log failures + warn on consecutive same-tool failures.
    #
    # 1. Appends a log line to error_log.md (original behavior)
    # 2. Tracks recent failures in /tmp/.nnd_tool_fail_history
    # 3. If the same tool fails 2+ times within 5 minutes, outputs a warning
    #    that tells the AI to switch to an alternative tool.
    #
    # Fails silent on any error.
    
    set -u
    
    log_dir="$HOME/.claude/memory"
    log_file="$log_dir/error_log.md"
    history_file="/tmp/.nnd_tool_fail_history"
    mkdir -p "$log_dir" 2>/dev/null || exit 0
    touch "$log_file" 2>/dev/null || exit 0
    
    ts="$(date '+%Y-%m-%d %H:%M')"
    now_epoch="$(date +%s)"
    
    if ! command -v jq >/dev/null 2>&1; then
      printf '[%s] TOOL_FAIL | unknown | jq_not_installed\n' "$ts" >> "$log_file"
      exit 0
    fi
    
    payload="$(cat)"
    tool_name="$(printf '%s' "$payload" | jq -r '.tool_name // empty' 2>/dev/null)" || tool_name=""
    error_summary="$(printf '%s' "$payload" | jq -r '.error // .stderr // .message // "unknown_error"' 2>/dev/null)" || error_summary=""
    
    if [ -z "$tool_name" ]; then
      printf '[%s] TOOL_FAIL | unknown | parse_error\n' "$ts" >> "$log_file"
      exit 0
    fi
    
    case "$tool_name" in
      Bash) category="BUILD_FAIL" ;;
      Write|Edit) category="FILE_FAIL" ;;
      *) category="TOOL_FAIL" ;;
    esac
    
    error_summary="$(printf '%s' "$error_summary" | tr '\n' ' ' | head -c 120)"
    printf '[%s] %s | %s | %s\n' "$ts" "$category" "$tool_name" "$error_summary" >> "$log_file"
    
    # --- Consecutive failure detection ---
    
    touch "$history_file" 2>/dev/null || exit 0
    
    # Append this failure
    printf '%s|%s|%s\n' "$now_epoch" "$tool_name" "$error_summary" >> "$history_file" 2>/dev/null
    
    # Clean entries older than 5 minutes
    cutoff=$((now_epoch - 300))
    if [ -f "$history_file" ]; then
      awk -F'|' -v cutoff="$cutoff" '$1 >= cutoff' "$history_file" > "${history_file}.tmp" 2>/dev/null && \
        mv "${history_file}.tmp" "$history_file" 2>/dev/null
    fi
    
    # Count recent failures for this tool
    count=0
    if [ -f "$history_file" ]; then
      count="$(grep -c "|${tool_name}|" "$history_file" 2>/dev/null)" || count=0
    fi
    
    if [ "$count" -ge 2 ]; then
      case "$tool_name" in
        Grep|Glob)
          alt="Bash with 'find' and 'grep -r'" ;;
        Read)
          alt="Bash with 'ls' to verify path first, then Read" ;;
        mcp__playwright__*|mcp__pinchtab__*|mcp__agent-browser__*)
          alt="a different browser tool, or Bash with 'curl'" ;;
        *)
          alt="Bash or an alternative approach" ;;
      esac
      printf '[no-no-debug] %s has failed %d times in 5 minutes. Switch to %s.\n' "$tool_name" "$count" "$alt"
    fi
    
    exit 0
    
  • hooks/review_reminder.pyGitHub
    Read the script
    #!/usr/bin/env python3
    """UserPromptSubmit hook — remind when error review is overdue.
    
    Reads ~/.claude/memory/error_tracker.md for the Last Review Date and the
    configured review frequency (default: 3 days). If enough days have elapsed,
    prints a one-line reminder that the AI will act on to start the Mechanism 3
    review cycle. A 4-hour cooldown (via a temp file) prevents the reminder from
    firing on every single user message within the same session.
    
    Fails silent on any error. A broken hook must never interrupt the user.
    """
    from __future__ import annotations
    
    import sys
    import os
    from datetime import datetime
    from pathlib import Path
    
    TRACKER = Path.home() / ".claude" / "memory" / "error_tracker.md"
    COOLDOWN_FILE = Path("/tmp/.nnd_review_reminded")
    COOLDOWN_HOURS = 4
    
    
    def main() -> int:
        try:
            sys.stdin.read()
        except Exception:
            pass
    
        if not TRACKER.exists():
            return 0
    
        if COOLDOWN_FILE.exists():
            try:
                mtime = COOLDOWN_FILE.stat().st_mtime
                if (datetime.now().timestamp() - mtime) / 3600 < COOLDOWN_HOURS:
                    return 0
            except Exception:
                pass
    
        try:
            content = TRACKER.read_text(encoding="utf-8")
        except Exception:
            return 0
    
        freq = 3
        for line in content.splitlines():
            if "review_frequency:" in line:
                try:
                    freq = int(line.split("review_frequency:")[1].strip().rstrip(" ->"))
                except (ValueError, IndexError):
                    pass
    
        for line in content.splitlines():
            if line.startswith("Last Review Date:"):
                date_str = line.split(":", 1)[1].strip()
                try:
                    last_review = datetime.strptime(date_str, "%Y-%m-%d")
                    days_since = (datetime.now() - last_review).days
                    if days_since >= freq:
                        print(
                            f"[no-no-debug] Error review overdue "
                            f"({days_since} days since last review on {date_str}). "
                            f"Run: error review"
                        )
                        try:
                            COOLDOWN_FILE.write_text(str(datetime.now()))
                        except Exception:
                            pass
                except ValueError:
                    pass
                break
    
        return 0
    
    
    if __name__ == "__main__":
        sys.exit(main())
    
  • hooks/user_prompt_filter.pyGitHub
    Read the script
    #!/usr/bin/env python3
    """UserPromptSubmit hook — log genuine user corrections to error_log.md.
    
    Claude Code delivers the UserPromptSubmit payload to this script via stdin
    as JSON. We extract the user's prompt, strip all XML-style context blocks
    (<system-reminder>, <task-notification>, <project-memory-context>, etc.)
    so that keywords appearing in system-injected context or past-work memory
    cannot false-trigger the detector, then match the remaining user-authored
    text against a tight list of second-person correction phrases.
    
    On a match, appends a single line to ~/.claude/memory/error_log.md:
        [YYYY-MM-DD HH:MM] USER_CORRECTION | <first 200 chars, whitespace flattened>
    
    Fails silent on any error. A broken hook must never interrupt the user.
    """
    from __future__ import annotations
    
    import json
    import re
    import sys
    from datetime import datetime
    from pathlib import Path
    
    LOG_PATH = Path.home() / ".claude" / "memory" / "error_log.md"
    
    # Tight second-person correction phrases only.
    # Bare `不对` / `错了` / `wrong` / `again` false-trigger on memory context,
    # code snippets, documentation and injected system reminders that happen to
    # contain the word, so they are NOT included here.
    PATTERNS = [
        r"你又错了",
        r"你错了",
        r"你搞错",
        r"你说错",
        r"不对啊",
        r"不对不对",
        r"这不对",
        r"这明显不对",
        r"不是这样",
        r"不是这个意思",
        r"不是这么",
        # Python 3's \b treats CJK chars as word chars, so we use ASCII-only
        # lookarounds instead — this avoids false matches where an English phrase
        # is embedded in Chinese text and vice versa.
        r"(?<![A-Za-z])wrong again(?![A-Za-z])",
        r"(?<![A-Za-z])that'?s wrong(?![A-Za-z])",
        r"(?<![A-Za-z])that'?s not right(?![A-Za-z])",
        r"(?<![A-Za-z])you already (did|said)(?![A-Za-z])",
        r"(?<![A-Za-z])nope(?![A-Za-z])",
    ]
    CORRECTION_RE = re.compile("|".join(PATTERNS), re.IGNORECASE)
    
    # Strip <tag>...</tag> blocks (with attributes), looping until stable so
    # adjacent / nested blocks all get removed, then drop any stray tags.
    XML_BLOCK_RE = re.compile(r"<([A-Za-z][\w:-]*)\b[^>]*>.*?</\1>", re.DOTALL)
    XML_STRAY_RE = re.compile(r"<[^>]+>")
    # Strip fenced code blocks (``` … ```) and inline code spans (` … `) so a
    # user pasting code that contains a correction phrase (e.g. a string literal
    # "wrong again") doesn't false-trigger.
    FENCED_CODE_RE = re.compile(r"```[\s\S]*?```", re.DOTALL)
    INLINE_CODE_RE = re.compile(r"`[^`\n]+`")
    WHITESPACE_RE = re.compile(r"\s+")
    
    # Normalise Unicode smart quotes and dashes to ASCII so macOS auto-correct
    # (' → ' / ’, " → “ / ”) doesn't cause matches like "that's wrong" to miss.
    SMART_CHARS = str.maketrans({
        "\u2018": "'", "\u2019": "'", "\u201A": "'", "\u201B": "'",
        "\u201C": '"', "\u201D": '"', "\u201E": '"', "\u201F": '"',
    })
    
    
    def strip_xml(text: str) -> str:
        prev = None
        while prev != text:
            prev = text
            text = XML_BLOCK_RE.sub(" ", text)
        text = XML_STRAY_RE.sub(" ", text)
        text = FENCED_CODE_RE.sub(" ", text)
        text = INLINE_CODE_RE.sub(" ", text)
        text = text.translate(SMART_CHARS)
        return WHITESPACE_RE.sub(" ", text).strip()
    
    
    def main() -> int:
        try:
            payload = json.load(sys.stdin)
        except Exception:
            return 0
        prompt = payload.get("prompt") or ""
        if not prompt:
            return 0
        cleaned = strip_xml(prompt)
        if not cleaned or not CORRECTION_RE.search(cleaned):
            return 0
        try:
            LOG_PATH.parent.mkdir(parents=True, exist_ok=True)
            excerpt = cleaned[:200].replace("\n", " ")
            stamp = datetime.now().strftime("%Y-%m-%d %H:%M")
            with LOG_PATH.open("a", encoding="utf-8") as fh:
                fh.write(f"[{stamp}] USER_CORRECTION | {excerpt}\n")
        except Exception:
            return 0
        return 0
    
    
    if __name__ == "__main__":
        sys.exit(main())
    

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 withno-no-debug

A self-evolution system for AI coding assistants "auto-triggers every 3 days" but had no enforcement mechanism — the review only ran when manually invoked, and the tracker went stale silently.

Get the whole plugin
Stats
151
Stars
7
Forks
Maintained
Maintenance
Python
Language
MIT
License
5mo ago
Last commit
6mo ago
Created
1d ago
Added

Repo: summerliuuu/no-no-debug