Skip to content
Productivity
Hook

Hooks

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

From plugin
bedrock
11010 skills1 hook

Claude Code

> /plugin marketplace add iurykrieger/claude-bedrock
> /plugin install bedrock@claude-bedrock

What fires, and when

Stop

  • python3 "${CLAUDE_PLUGIN_ROOT}/hooks/error_reporter.py"
Read hooks/hooks.json

In the plugin's words

How bedrock describes its own hook set.

Auto-report bedrock framework errors as GitHub issues

Where it lives

  • hooks/error_reporter.pyRunsGitHub
    Read the script
    #!/usr/bin/env python3
    """Bedrock error reporter hook — auto-creates GitHub issues for framework errors.
    
    Stop hook entrypoint. Reads transcript via stdin JSON, scans the last turn
    for bedrock framework errors (technical + logical), opens deduplicated issues
    on iurykrieger/claude-bedrock via the gh CLI.
    
    Never raises, always exits 0. Failures log to ~/.claude-bedrock-cache/error-reporter.log.
    """
    import hashlib
    import json
    import os
    import platform
    import re
    import subprocess
    import sys
    import time
    from datetime import datetime, timezone
    from pathlib import Path
    from typing import Iterable
    
    
    # Keep window small for performance: only the most recent N lines matter,
    # since hook fires per turn and older lines are from prior turns.
    _TRANSCRIPT_TAIL_LINES = 200
    
    _BEDROCK_INVOCATION_RE = re.compile(r"/bedrock:(\w+)")
    
    _TRACEBACK_RE = re.compile(r"Traceback \(most recent call last\):", re.MULTILINE)
    _LAST_FRAME_RE = re.compile(r'File "([^"]+)", line (\d+), in (\w+)\n((?!\s*File ")[^\n]*\n)?([A-Z][\w\.]+(?:Error|Exception):.*)', re.MULTILINE)
    
    # Regex catalog. ID -> compiled regex. Keep small to avoid false positives.
    _LOGICAL_ERROR_CATALOG = {
        "graphify_invalid": re.compile(r"graphify.{0,40}(returned|gave|produced).{0,20}invalid", re.IGNORECASE),
        "vault_corrupt": re.compile(r"vault\.json.{0,30}corrupt", re.IGNORECASE),
        "skill_failure": re.compile(r"bedrock\s+\w+\s+(skill\s+)?failed", re.IGNORECASE),
        "entity_unwritable": re.compile(r"failed\s+to\s+(write|persist)\s+entity", re.IGNORECASE),
        "sync_unauthorized": re.compile(r"(sync.{0,30}unauthorized|auth(?:entication)?\s+failed.{0,30}sync)", re.IGNORECASE),
    }
    
    # Redaction regexes — order matters in redact(). See function docstring.
    _HOME_PATH_RE = re.compile(r"(?:/Users/[^/\s]+|/home/[^/\s]+|/root)(?:/[^/\s]+)*?(?=/\.claude/plugins/[^/\s]+/[^/\s]+)")
    _GENERIC_HOME_PATH_RE = re.compile(r"(?:/Users/[^/\s]+|/home/[^/\s]+|/root)")
    _PLUGIN_PREFIX_RE = re.compile(r"\.claude/plugins/[^/\s]+/[^/\s]+/")
    _UUID_RE = re.compile(r"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}", re.IGNORECASE)
    _URL_RE = re.compile(r"https?://[^\s'\"<>)]+", re.IGNORECASE)
    _ISO_TIMESTAMP_RE = re.compile(r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+\-]\d{2}:?\d{2})?")
    _VAULT_ENTITY_FILE_RE = re.compile(r"\b(?:people|teams|actors|concepts|topics|discussions|projects|fleeting)/[\w-]+\.md\b")
    _EMAIL_RE = re.compile(r"[\w.+-]+@[\w-]+\.[\w.-]+")
    _CC_LIKE_RE = re.compile(r"\b\d[\d -]{11,17}\d\b")
    _API_KEY_RE = re.compile(r"\b(?:sk_(?:live|test)|ghp|ghs|gho|ghu|ghr|xox[abps]|AKIA|ASIA|GITHUB_TOKEN)[\w_-]{10,}\b", re.IGNORECASE)
    _BARE_WIKILINK_RE = re.compile(r"\[\[[\w/-]+\]\]")
    
    
    def _read_transcript_tail(transcript_path: Path) -> str:
        """Read up to the last _TRANSCRIPT_TAIL_LINES of a JSONL transcript.
    
        Returns empty string if the file does not exist or is unreadable.
        """
        try:
            with open(transcript_path, "r", encoding="utf-8", errors="replace") as f:
                lines = f.readlines()
        except (FileNotFoundError, OSError):
            return ""
        return "".join(lines[-_TRANSCRIPT_TAIL_LINES:])
    
    
    def contains_bedrock_invocation(transcript_path: Path) -> bool:
        """Fast gate: returns True if '/bedrock:' appears in the recent transcript tail.
    
        Intentionally a substring check, not JSON parsing — optimized for the 99% case
        where the answer is no. False positives (e.g., the substring quoted in an
        assistant message) only cost an extra slow-path traversal in the next stage;
        false negatives would mean lost error reports, which is the worse failure mode.
        """
        tail = _read_transcript_tail(Path(transcript_path))
        return "/bedrock:" in tail
    
    
    def is_reporting_enabled(start_dir: Path) -> bool:
        """Walk up from start_dir looking for .bedrock/config.json.
    
        Returns True (default) if no config found, config malformed, or field missing.
        Returns False only if config explicitly sets error_reporting to the JSON `false`
        boolean — null, "false" strings, 0, etc. all default-on. Opt-out must be
        well-formed and intentional.
        """
        current = Path(start_dir).resolve()
        for candidate in [current, *current.parents]:
            cfg_path = candidate / ".bedrock" / "config.json"
            if cfg_path.is_file():
                try:
                    with open(cfg_path, "r", encoding="utf-8") as f:
                        cfg = json.load(f)
                except (json.JSONDecodeError, OSError):
                    return True  # default-on if config unreadable
                val = cfg.get("error_reporting", True)
                return val if isinstance(val, bool) else True
        return True
    
    
    def _iter_transcript_lines(transcript_path: Path) -> Iterable[dict]:
        """Yield parsed JSON objects from each non-empty JSONL line. Skips malformed lines."""
        try:
            with open(transcript_path, "r", encoding="utf-8", errors="replace") as f:
                for line in f:
                    line = line.strip()
                    if not line:
                        continue
                    try:
                        yield json.loads(line)
                    except json.JSONDecodeError:
                        continue
        except (FileNotFoundError, OSError):
            return
    
    
    def _iter_last_turn_lines(transcript_path: Path) -> list[dict]:
        """Return parsed JSON entries from the last turn.
    
        A turn boundary is defined by a role=user entry containing a type=text content
        block (i.e., a user-typed message, not a tool_result). Returns the last such
        entry and everything after it. If no boundary is found, returns all entries.
        """
        entries = list(_iter_transcript_lines(transcript_path))
        if not entries:
            return []
    
        # Walk backwards to find the last user-typed message
        last_user_text_idx = None
        for idx in range(len(entries) - 1, -1, -1):
            entry = entries[idx]
            if entry.get("role") != "user":
                continue
            for block in entry.get("message", {}).get("content", []) or []:
                if isinstance(bl

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.

@iurykrieger
Ships withbedrock

Second Brain automation for Obsidian vaults — entity management, ingestion, compression, and sync via Claude Code skills

Stars on GitHub

Momentum

stars in total
110
Star history of iurykrieger/claude-bedrock on GitHub, a chart by star-history.com

More plugins that ship hooks.

i-have-adhd

i-have-adhd

A skill that shapes Claude Code's output for readers with ADHD: it leads with the next action, numbers multi-step work, restates state across turns, drops tangents, gives specific time estimates, and makes progress visible. Invoke it with /i-h.

1 skill1 hook60k
@ayghri@ayghri
Install Plugin
pua

pua

A Claude Code plugin that makes the assistant work harder when it stalls or repeats failures. It adds pressure-style coaching, plus skills for workplace reminders and senior engineer, CTO and team-lead modes for planning and execution.

12 skills+320k
@tanweai@tanweai
Install Plugin
claude-obsidian

claude-obsidian

Turns the sources you read, such as papers, articles and transcripts, into a linked wiki of Markdown notes in Obsidian. Claude keeps sources and citations so every summary can be traced back to its origin.

15 skills+216k
@agricidaniel@agricidaniel
Install Plugin
obsidian-second-brain

obsidian-second-brain

Gives Claude Code a persistent second brain stored as plain markdown in an Obsidian vault. Notes rewrite themselves as new sources arrive, and scheduled agents handle research synthesis and note capture.

1 skill+25.3k
@eugeniughelbur@eugeniughelbur
Install Plugin
arscontexta

arscontexta

A Claude Code plugin that builds a personal knowledge system through conversation, with skills for adding knowledge domains, checking vault health, getting methodology guidance, and evolving the system over time.

10 skills+23.5k
@agenticnotetaking@agenticnotetaking
Install Plugin
pro-workflow

pro-workflow

A Claude Code workflow plugin that learns from your corrections and keeps a self-correcting memory across sessions. It adds skills for agent teams, parallel worktree batch changes, bug capture as GitHub issues, project auto-setup and context compaction.

41 skills+33k
@rohitg00@rohitg00
Install Plugin
engram

engram

Helps you learn any topic with a first-principles curriculum, guided tutoring, and spaced review that schedules free-recall practice. Includes coaching for retention stats and study strategy.

3 skills+21.5k
@nagisanzenin@nagisanzenin
Install Plugin
hey

hey

A command line tool for HEY that lets you read and send email, manage contacts, labels, calendars, todos, habits, time tracking and journal entries from the terminal or from your coding agent.

1 skill1 hook423
@basecamp@basecamp
Install Plugin

Questions about the hooks in bedrock.

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