Skip to content
Development
Hook

Hooks

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

From plugin
devteam
26813 agents5 hooks

Claude Code

> /plugin marketplace add NYCU-Chung/my-claude-devteam
> /plugin install devteam@my-claude-devteam

What fires, and when

PreToolUse

  • MatchesBashinput=$(cat | jq -r '.input // empty'); if echo "$input" | grep -qE 'rm\s+(-[a-zA-Z]*r[a-zA-Z]*f|--recursive.*--force|-rf)'; then echo 'BLOCKED: rm -rf is dangerous' >&2; exit 2; fi; exit 0input=$(cat | jq -r '.input // empty'); if echo "$input" | grep -qE 'git\s+push.*(-f|--force).*\s+(main|master)'; then echo 'BLOCKED: Force pushing to main/master is not allowed.' >&2; exit 2; fi; exit 0node -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{const i=JSON.parse(d);const cmd=i.tool_input?.command||'';if(/--no-verify/.test(cmd)){process.stderr.write('[devteam] BLOCKED: --no-verify is not allowed. Let git hooks run.\n');process.exit(2)}process.stdout.write(d)})"node ${CLAUDE_PLUGIN_ROOT}/hooks/branch-protection.jsnode ${CLAUDE_PLUGIN_ROOT}/hooks/commit-quality.js
  • MatchesWrite|Editfilepath=$(cat | jq -r '.file_path // .filePath // empty'); if echo "$filepath" | grep -qE '\.(env|pem|key)$|/secrets/|credentials'; then echo "BLOCKED: Cannot modify sensitive file: $filepath" >&2; exit 2; fi; exit 0node ${CLAUDE_PLUGIN_ROOT}/hooks/config-protection.jsnode ${CLAUDE_PLUGIN_ROOT}/hooks/suggest-compact.js
  • Matchesmcp__.*node ${CLAUDE_PLUGIN_ROOT}/hooks/mcp-health.js pre
  • MatchesReadnode ${CLAUDE_PLUGIN_ROOT}/hooks/large-file-warner.js

PostToolUse

  • Matches.*bash ${CLAUDE_PLUGIN_ROOT}/hooks/log-error.sh
  • MatchesBashnode ${CLAUDE_PLUGIN_ROOT}/hooks/audit-log.js
  • MatchesWrite|Editnode ${CLAUDE_PLUGIN_ROOT}/hooks/accumulator.jsnode ${CLAUDE_PLUGIN_ROOT}/hooks/design-quality.jsnode ${CLAUDE_PLUGIN_ROOT}/hooks/test-runner.js

PostToolUseFailure

  • Matchesmcp__.*node ${CLAUDE_PLUGIN_ROOT}/hooks/mcp-health.js failure

Stop

  • Matches*node ${CLAUDE_PLUGIN_ROOT}/hooks/batch-format.jsnode ${CLAUDE_PLUGIN_ROOT}/hooks/check-console.jsnode ${CLAUDE_PLUGIN_ROOT}/hooks/cost-tracker.jsnode ${CLAUDE_PLUGIN_ROOT}/hooks/session-summary.js

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.

  • Matchesstartup|resumeecho '[devteam] Dev team online — 12 agents + 15 hooks active. Three red lines: closure discipline, fact-driven, exhaustiveness.' >&2; exit 0
Read hooks/hooks.json

In the plugin's words

How devteam describes its own hook set.

Claude Code Dev Team hooks (15 total): commit quality, cost tracking, MCP health, config protection, design quality, batch format, audit log, compact suggestion, error logging, test runner, branch protection, large file warner, session summary.

Where it lives

  • hooks/accumulator.jsRunsGitHub
    Read the script
    // Accumulate edited JS/TS file paths for batch format+typecheck at Stop
    const crypto = require('crypto');
    const fs = require('fs');
    const os = require('os');
    const path = require('path');
    let d = ''; process.stdin.on('data', c => d += c);
    process.stdin.on('end', () => {
      try {
        const i = JSON.parse(d);
        const sid = (process.env.CLAUDE_SESSION_ID ||
          crypto.createHash('sha1').update(process.cwd()).digest('hex').slice(0, 12))
          .replace(/[^a-zA-Z0-9_-]/g, '_').slice(0, 64);
        const af = path.join(os.tmpdir(), `claude-edited-${sid}.txt`);
        const files = [];
        if (i.tool_input?.file_path) files.push(i.tool_input.file_path);
        if (Array.isArray(i.tool_input?.edits))
          for (const e of i.tool_input.edits) if (e?.file_path) files.push(e.file_path);
        for (const fp of files) {
          if (/\.(ts|tsx|js|jsx)$/.test(fp)) {
            fs.appendFileSync(af, fp + '\n');
          }
        }
      } catch (e) {}
      process.stdout.write(d);
    });
    
  • hooks/audit-log.jsRunsGitHub
    Read the script
    // Audit log all bash commands with auto-redaction of secrets
    const fs = require('fs');
    const os = require('os');
    const path = require('path');
    let d = ''; process.stdin.on('data', c => d += c);
    process.stdin.on('end', () => {
      try {
        const i = JSON.parse(d);
        const cmd = String(i.tool_input?.command || '?')
          .replace(/\n/g, ' ')
          .replace(/--token[= ][^ ]*/g, '--token=<REDACTED>')
          .replace(/password[= ][^ ]*/gi, 'password=<REDACTED>')
          .replace(/\bghp_[A-Za-z0-9_]+\b/g, '<REDACTED>')
          .replace(/\bgho_[A-Za-z0-9_]+\b/g, '<REDACTED>')
          .replace(/sshpass\s+-p\s+'[^']*'/g, "sshpass -p '<REDACTED>'")
          .replace(/AIza[a-zA-Z0-9_-]{35}/g, '<REDACTED>');
        const logDir = path.join(os.homedir(), '.claude');
        fs.mkdirSync(logDir, { recursive: true });
        fs.appendFileSync(
          path.join(logDir, 'bash-commands.log'),
          `[${new Date().toISOString()}] ${cmd}\n`
        );
      } catch (e) {}
      process.stdout.write(d);
    });
    
  • hooks/batch-format.jsRunsGitHub
    Read the script
    // Stop hook: Batch format (Prettier) and typecheck (tsc) all JS/TS files edited this response
    const crypto = require('crypto');
    const { spawnSync } = require('child_process');
    const fs = require('fs');
    const os = require('os');
    const path = require('path');
    
    let d = ''; process.stdin.on('data', c => d += c);
    process.stdin.on('end', () => {
      try {
        const sid = (process.env.CLAUDE_SESSION_ID ||
          crypto.createHash('sha1').update(process.cwd()).digest('hex').slice(0, 12))
          .replace(/[^a-zA-Z0-9_-]/g, '_').slice(0, 64);
        const af = path.join(os.tmpdir(), `claude-edited-${sid}.txt`);
    
        let raw;
        try { raw = fs.readFileSync(af, 'utf8'); } catch (e) { process.stdout.write(d); return; }
        try { fs.unlinkSync(af); } catch (e) {}
    
        const files = [...new Set(raw.split('\n').map(l => l.trim()).filter(Boolean))]
          .filter(f => /\.(ts|tsx|js|jsx)$/.test(f) && fs.existsSync(f));
    
        if (files.length === 0) { process.stdout.write(d); return; }
    
        // Try Prettier
        const isWin = process.platform === 'win32';
        const prettierBin = path.join(process.cwd(), 'node_modules', '.bin', isWin ? 'prettier.cmd' : 'prettier');
        if (fs.existsSync(prettierBin)) {
          try {
            spawnSync(prettierBin, ['--write', ...files], {
              shell: isWin, stdio: 'pipe', timeout: 60000
            });
          } catch (e) {}
        }
    
        // TypeCheck TS files
        const tsFiles = files.filter(f => /\.(ts|tsx)$/.test(f));
        if (tsFiles.length > 0) {
          try {
            const npx = isWin ? 'npx.cmd' : 'npx';
            const r = spawnSync(npx, ['tsc', '--noEmit', '--pretty', 'false'], {
              shell: isWin, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'], timeout: 120000
            });
            if (r.status !== 0) {
              const lines = ((r.stdout || '') + (r.stderr || '')).split('\n');
              for (const f of tsFiles) {
                const rel = path.relative(process.cwd(), f);
                const relevant = lines.filter(l => l.includes(f) || l.includes(rel)).slice(0, 3);
                if (relevant.length > 0) {
                  process.stderr.write(`[Hook] TS errors in ${path.basename(f)}:\n`);
                  relevant.forEach(l => process.stderr.write(l + '\n'));
                }
              }
            }
          } catch (e) {}
        }
      } catch (e) {}
      process.stdout.write(d);
    });
    
  • hooks/branch-protection.jsRunsGitHub
    Read the script
    // PreToolUse on Bash: warn (and sometimes block) operations on protected branches.
    // Protected branches: main, master, production, release, prod
    // Behavior:
    //   - HARD BLOCK: any push --force to a protected branch
    //   - HARD BLOCK: git commit directly on a protected branch (no -m needed to detect)
    //   - WARN: any other git operation on a protected branch
    const { spawnSync } = require('child_process');
    
    const PROTECTED = /^(main|master|production|release|prod)$/;
    
    let d = '';
    process.stdin.on('data', c => d += c);
    process.stdin.on('end', () => {
      try {
        const i = JSON.parse(d);
        const cmd = String(i.tool_input?.command || '');
        if (!/\bgit\b/.test(cmd)) { process.stdout.write(d); return; }
    
        // Get current branch (best-effort)
        let branch = '';
        try {
          const r = spawnSync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { encoding: 'utf8', timeout: 5000 });
          branch = (r.stdout || '').trim();
        } catch (e) {}
    
        const onProtected = PROTECTED.test(branch);
    
        // Hard block: force push to protected branch (regardless of which branch we're on)
        if (/git\s+push.*(--force|--force-with-lease|-f\b).*(main|master|production|release|prod)/.test(cmd)) {
          process.stderr.write(`[Hook] BLOCKED: Force push to a protected branch is not allowed.\n`);
          process.exit(2);
        }
    
        // Hard block: commit directly on a protected branch
        if (onProtected && /git\s+commit\b/.test(cmd) && !/--amend.*--no-edit/.test(cmd)) {
          process.stderr.write(`[Hook] BLOCKED: You are on '${branch}' (protected). Create a feature branch first:\n`);
          process.stderr.write(`        git checkout -b your-feature-name\n`);
          process.exit(2);
        }
    
        // Warn: any other git mutation on a protected branch
        if (onProtected && /git\s+(merge|rebase|reset|cherry-pick|revert|checkout\s+[^-])/.test(cmd)) {
          process.stderr.write(`[Hook] WARNING: You are on '${branch}' (protected). Make sure this is intended.\n`);
        }
      } catch (e) {}
      process.stdout.write(d);
    });
    
  • hooks/check-console.jsRunsGitHub
    Read the script
    // Stop hook: Check for console.log in modified files
    const { spawnSync } = require('child_process');
    const fs = require('fs');
    let d = ''; process.stdin.on('data', c => d += c);
    process.stdin.on('end', () => {
      try {
        const r = spawnSync('git', ['diff', '--name-only', '--diff-filter=ACMR', 'HEAD'], { encoding: 'utf8' });
        if (r.status !== 0) { process.stdout.write(d); return; }
    
        const excluded = [/\.test\.[jt]sx?$/, /\.spec\.[jt]sx?$/, /\.config\.[jt]s$/, /scripts\//, /__tests__\//];
        const files = r.stdout.trim().split('\n')
          .filter(f => f && /\.[jt]sx?$/.test(f) && !excluded.some(p => p.test(f)) && fs.existsSync(f));
    
        let found = false;
        for (const f of files) {
          const c = fs.readFileSync(f, 'utf8');
          const lines = c.split('\n');
          const matches = [];
          lines.forEach((line, i) => {
            if (/console\.log\b/.test(line) && !/\/\//.test(line.split('console.log')[0])) {
              matches.push(i + 1);
            }
          });
          if (matches.length > 0) {
            process.stderr.write(`[Hook] console.log in ${f} (lines: ${matches.slice(0, 5).join(', ')})\n`);
            found = true;
          }
        }
        if (found) process.stderr.write('[Hook] Remove console.log before committing\n');
      } catch (e) {}
      process.stdout.write(d);
    });
    
  • hooks/commit-quality.jsRunsGitHub
    Read the script
    // Pre-commit quality check: block debugger statements and hardcoded secrets in staged files
    const { spawnSync } = require('child_process');
    let d = ''; process.stdin.on('data', c => d += c);
    process.stdin.on('end', () => {
      try {
        const i = JSON.parse(d);
        const cmd = i.tool_input?.command || '';
        if (!/git commit/.test(cmd) || /--amend/.test(cmd)) { process.stdout.write(d); return; }
    
        const r = spawnSync('git', ['diff', '--cached', '--name-only', '--diff-filter=ACMR'], { encoding: 'utf8' });
        const files = (r.stdout || '').trim().split('\n').filter(Boolean);
        let blocked = false;
    
        for (const f of files) {
          if (!/\.(js|jsx|ts|tsx|py)$/.test(f)) continue;
          const cr = spawnSync('git', ['show', ':' + f], { encoding: 'utf8' });
          const c = cr.stdout || '';
    
          if (/\bdebugger\b/.test(c)) {
            process.stderr.write(`[Hook] ERROR: debugger statement in ${f}\n`);
            blocked = true;
          }
    
          const secrets = [
            /sk-[a-zA-Z0-9]{20,}/,
            /ghp_[a-zA-Z0-9]{36}/,
            /gho_[a-zA-Z0-9]{36}/,
            /AKIA[A-Z0-9]{16}/,
            /AIza[a-zA-Z0-9_-]{35}/,
          ];
          for (const p of secrets) {
            if (p.test(c)) {
              process.stderr.write(`[Hook] ERROR: potential secret in ${f}\n`);
              blocked = true;
            }
          }
        }
    
        if (blocked) {
          process.stderr.write('[Hook] Commit blocked. Fix issues above.\n');
          process.exit(2);
        }
      } catch (e) {}
      process.stdout.write(d);
    });
    
  • hooks/config-protection.jsRunsGitHub
  • hooks/cost-tracker.jsRunsGitHub
  • hooks/design-quality.jsRunsGitHub
  • hooks/large-file-warner.jsRunsGitHub
  • hooks/log-error.shRunsGitHub
  • hooks/mcp-health.jsRunsGitHub
  • hooks/session-summary.jsRunsGitHub
  • hooks/suggest-compact.jsRunsGitHub
  • hooks/test-runner.jsRunsGitHub

All 15 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.

@nycu-chung
Ships withdevteam

An entire engineering team for Claude Code — 12 specialized agents, 15 automation hooks, and the P7/P9/P10 methodology that keeps them disciplined. Most people use Claude Code as a single coder.

Stars on GitHub

Momentum

stars in total
268
Star history of NYCU-Chung/my-claude-devteam 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 devteam.

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