# How to Configure Hooks for Automated Workflows in ECC: A Complete Guide

> Automate quality gates and safety checks in ECC by configuring hooks. Learn to run custom Node.js commands before or after Claude Code tool invocations and enforce workflows.

- Repository: [Affaan Mustafa/ECC](https://github.com/affaan-m/ECC)
- Tags: how-to-guide
- Published: 2026-05-26

---

**Everything Claude Code (ECC) provides an event-driven hook system that runs custom Node.js commands automatically before or after Claude Code tool invocations, enabling automated quality gates, safety checks, and workflow enforcement without manual intervention.**

The open-source **affaan-m/ECC** repository ships with a declarative hook framework that intercepts Claude Code operations at five distinct lifecycle phases. By configuring hooks in [`hooks/hooks.json`](https://github.com/affaan-m/ECC/blob/main/hooks/hooks.json), you can automate repetitive checks, block dangerous commands, and persist session state across your development workflow.

## Understanding the Hook Architecture

ECC implements a JSON-driven hook system validated against the official Claude Code settings schema. The master configuration resides in [`hooks/hooks.json`](https://github.com/affaan-m/ECC/blob/main/hooks/hooks.json) and defines matchers, commands, and execution phases.

### Hook Lifecycle Phases

The system fires hooks during five specific execution windows:

- **PreToolUse**: Executes immediately before tools like `Bash`, `Edit`, or `Write` run. Use this phase to block dangerous commands or enforce policy violations.
- **PostToolUse**: Runs right after tool completion. Ideal for quality gates, logging PR URLs, or detecting design drift.
- **PreCompact**: Fires before Claude compacts session context. Use for stashing pending edits or persisting transient state.
- **Stop**: Triggers after every Claude response, even when no tool is used. Perfect for batch formatting, type-checking, or audit logging.
- **SessionStart / SessionEnd**: Bookend phases for loading prior context, detecting package managers, or emitting lifecycle analytics.

### Configuration Schema

Each hook entry in [`hooks/hooks.json`](https://github.com/affaan-m/ECC/blob/main/hooks/hooks.json) requires three components:

1. **`matcher`**: A glob-style pattern selecting target tools (e.g., `Bash|Write|Edit`)
2. **`hooks`**: An array of command objects executing as Node.js one-liners
3. **`description`**: Human-readable text for diagnostics

Commands receive the tool's JSON payload on **stdin** and must echo the same JSON to **stdout**. Exit code `2` blocks execution in PreToolUse hooks, while code `0` allows continued operation.

```json
{
  "$schema": "https://json.schemastore.org/claude-code-settings.json",
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "node path/to/script.js",
            "description": "Block dangerous commands"
          }
        ]
      }
    ]
  }
}

```

## Installing and Enabling Hooks

You must install hooks via ECC's provided installer rather than manually copying JSON files. The installer rewrites paths in [`hooks.json`](https://github.com/affaan-m/ECC/blob/main/hooks.json) to point at your Claude config directory (`~/.claude`).

Install the hooks runtime using the appropriate script for your platform:

```bash

# Linux/macOS

bash ./install.sh --target claude --modules hooks-runtime

# Windows PowerShell

pwsh -File .\install.ps1 --target claude --modules hooks-runtime

```

After installation, the resolved configuration appears at `~/.claude/hooks/hooks.json`. Manual copying of raw JSON into `~/.claude/settings.json` will break path resolution and cause hook failures.

## Core Hook Categories

ECC ships with predefined hook groups targeting specific automation needs.

### PreToolUse: Safety and Governance

These hooks enforce guardrails before tools execute:

- **`pre:bash:dispatcher`**: Consolidates quality checks, tmux warnings, and GateGuard validation for Bash commands
- **`pre:write:doc-file-warning`**: Warns when creating non-standard markdown files (e.g., stray `.txt` files)
- **`pre:config-protection`**: Blocks modifications to linter or formatter configs, forcing code fixes instead of configuration changes
- **`pre:governance-capture`**: Captures secrets or policy violations when `ECC_GOVERNANCE_CAPTURE=1` is enabled

### PostToolUse: Automated Quality Gates

Post-execution hooks analyze completed operations:

- **`post:bash:dispatcher`**: Logs PR URLs and runs async build analysis
- **`post:quality-gate`**: Executes fast lint and test checks on edited files
- **`post:edit:design-quality-check`**: Warns when UI edits appear "template-like" or generic
- **`post:edit:accumulator`**: Collects edited paths for batch processing during the Stop phase

### Stop: Batch Operations

Stop-phase hooks run once per response to avoid per-edit overhead:

- **`stop:format-typecheck`**: Runs Prettier or Biome plus `tsc --noEmit` on all files edited during the current response
- **`stop:check-console-log`**: Scans modified files for stray `console.log` statements
- **`stop:session-end`**: Persists session transcripts and metric aggregates

### Session Lifecycle Hooks

- **`session:start`**: Detects active package managers (npm, pnpm, yarn) and loads persisted context
- **`session:end:marker`**: Emits lifecycle markers for downstream analytics

## Customizing Your Hook Configuration

ECC provides multiple methods to tailor hook behavior without modifying core files.

### Disabling Specific Hooks

Edit the installed `~/.claude/hooks/hooks.json` to remove unwanted entries. Alternatively, override selectively in `~/.claude/settings.json` by providing empty hook arrays:

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Write",
        "hooks": [],
        "description": "Override: allow all .md file creation"
      }
    ]
  }
}

```

### Runtime Environment Controls

Control hook profiles without touching JSON configuration using environment variables:

```bash
export ECC_HOOK_PROFILE=strict        # Options: minimal, standard, strict

export ECC_DISABLED_HOOKS="pre:bash:tmux-reminder,post:edit:typecheck"
export ECC_GATEGUARD=off              # Disable GateGuard fact-forcing

export ECC_SESSION_START_MAX_CHARS=4000  # Limit startup context injection

```

Set `ECC_GOVERNANCE_CAPTURE=1` to enable secret detection and policy violation capturing.

### Creating Custom Hooks

Implement custom automation by writing Node.js scripts that read from `stdin` and write to `stdout`. The script receives the tool's JSON payload and must return it unmodified to continue execution.

Example hook blocking destructive `rm -rf /` commands:

```javascript
// danger-guard.js
let data = '';
process.stdin.on('data', chunk => data += chunk);
process.stdin.on('end', () => {
  const input = JSON.parse(data);
  if (input.tool_name === 'Bash' && /rm\s+-rf\s+\//.test(input.tool_input.command)) {
    console.error('[Hook] BLOCKED: Dangerous rm command detected');
    process.exit(2);               // Blocks execution in PreToolUse
  }
  console.log(data);               // Must output original payload
});

```

Register the hook in [`hooks.json`](https://github.com/affaan-m/ECC/blob/main/hooks.json):

```json
{
  "matcher": "Bash",
  "hooks": [
    {
      "type": "command",
      "command": "node danger-guard.js",
      "description": "Block destructive rm commands"
    }
  ]
}

```

## Practical Hook Examples

### Blocking Large File Creation

Prevent creation of monolithic modules exceeding 800 lines:

```json
{
  "matcher": "Write",
  "hooks": [{
    "type": "command",
    "command": "node -e \"let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{const i=JSON.parse(d);const lines=(i.tool_input.content||'').split('\\n').length;if(lines>800){console.error('[Hook] BLOCKED: File exceeds 800 lines');process.exit(2)}console.log(d)})\""
  }],
  "description": "Block creation of files larger than 800 lines"
}

```

### Enforcing Test File Creation

Require [`.test.ts`](https://github.com/affaan-m/ECC/blob/main/.test.ts) files alongside new source files to encourage TDD:

```json
{
  "matcher": "Write",
  "hooks": [{
    "type": "command",
    "command": "node -e \"const fs=require('fs');let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{const i=JSON.parse(d);const p=i.tool_input.file_path||'';if(/src\\/.*\\.(ts|js)$/.test(p)&&!/\\.test\\.|\\.spec\\./.test(p)){const testPath=p.replace(/\\.(ts|js)$/,'.test.$1');if(!fs.existsSync(testPath)){console.error('[Hook] No test file for '+p);console.error('[Hook] Expected: '+testPath)}}console.log(d)})\""
  }],
  "description": "Remind to create tests when adding new source files"
}

```

### Preventing Git Amend

Block `git commit --amend` to enforce linear history:

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "node -e \"let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{const i=JSON.parse(d);if(/git\\s+commit/.test(i.tool_input.command)&&/--amend/.test(i.tool_input.command)){console.error('[Hook] BLOCKED: --amend is disallowed');process.exit(2)}console.log(d)})\"",
            "description": "Disallow git commit --amend"
          }
        ]
      }
    ]
  }
}

```

## Summary

- **Hook phases**: Configure automation during PreToolUse (blocking), PostToolUse (validation), Stop (batching), or Session lifecycle events
- **Installation**: Use [`install.sh`](https://github.com/affaan-m/ECC/blob/main/install.sh) or `install.ps1` with `--modules hooks-runtime` to properly resolve paths in `~/.claude/hooks/hooks.json`
- **Configuration**: Define matchers, commands, and descriptions in [`hooks/hooks.json`](https://github.com/affaan-m/ECC/blob/main/hooks/hooks.json) following the Claude Code settings schema
- **Customization**: Override behavior via `~/.claude/settings.json`, environment variables (`ECC_HOOK_PROFILE`, `ECC_DISABLED_HOOKS`), or custom Node.js scripts
- **Execution model**: Commands read JSON from stdin and must echo it to stdout; exit code 2 blocks execution in PreToolUse hooks
- **Observability**: Hook output appears in Claude's console prefixed with `[Hook]`; async logs write to `~/.claude/logs`

## Frequently Asked Questions

### How do I install ECC hooks on a new machine?

Run the platform-specific installer from the ECC repository root. On Linux or macOS, execute `bash ./install.sh --target claude --modules hooks-runtime`. On Windows, use `pwsh -File .\install.ps1 --target claude --modules hooks-runtime`. The installer rewrites path references in [`hooks/hooks.json`](https://github.com/affaan-m/ECC/blob/main/hooks/hooks.json) and places the configuration in `~/.claude/hooks/`.

### Can I block dangerous commands automatically?

Yes. Create a PreToolUse hook with a matcher targeting `Bash` tools. In your Node.js script, inspect `input.tool_input.command` for dangerous patterns (like `rm -rf /`). Write an error message to stderr and exit with code `2` to block execution, or code `0` to allow the command with a warning.

### How do I temporarily disable specific hooks without editing JSON files?

Set the `ECC_DISABLED_HOOKS` environment variable to a comma-separated list of hook IDs. For example, `export ECC_DISABLED_HOOKS="pre:bash:tmux-reminder,post:edit:typecheck"` disables those specific checks while preserving the rest of your configuration. You can also switch entire profiles using `ECC_HOOK_PROFILE=minimal`.

### Where are hook execution logs stored?

Hook output appears inline in Claude's console prefixed with `[Hook]`. Asynchronous hooks (like `post:bash:dispatcher`) write their logs to `~/.claude/logs/`. The `post:observe:continuous-learning` hook specifically manages continuous learning observations and audit trails for background operations.