Hooks

Hooks are Node.js scripts attached to Claude Code lifecycle events. They inject context and manage session state. Brewcode registers 2 hooks in hooks.json.

Summary table

#HookEventMatcherChannelTimeoutPurpose
1forced-evalUserPromptSubmitadditionalContext1sManager-role + split-discipline reminder
2session-startSessionStartadditionalContext3sSession init, permission_mode tag

Execution flow

UserPromptSubmit
  └── forced-eval.mjs          [ROLE] delegate-to-expert + [SPLIT] bounded units

SessionStart
  └── session-start.mjs        Version check, plan-symlink, permission_mode tag

Hook files

brewcode/hooks/
  forced-eval.mjs              UserPromptSubmit handler
  session-start.mjs            SessionStart handler
  hooks.json                   Hook registry (event bindings)
  lib/
    utils.mjs                  Shared I/O, configuration, logging

I/O protocol

All hooks follow a unified protocol:

  1. Read JSON from stdin (via readStdin())
  2. Receive fields: session_id, cwd, source (SessionStart), permission_mode (all events)
  3. Output JSON to stdout (via output())
  4. Write logs to stderr

Shared utilities:

  • hooks/lib/utils.mjs — I/O, configuration, logging

Detailed description

1. forced-eval.mjs

UserPromptSubmit additionalContext

Keeps delegation discipline in front of the model. Intercepts every user prompt and appends two short lines: the Manager role rule and the split rule. There is no skill-activation nudge — modern models pick skills on their own.

ParameterValue
EventUserPromptSubmit
ChanneladditionalContext
Timeout1000 ms

Exact injected text:

[ROLE] Manager: scan agents (project .claude/agents/ first) - expert for this domain exists -> delegate regardless of size; no expert or trivial one-off -> self.
[SPLIT] One agent for an hour = drift you cannot observe: split into bounded units (1 deliverable, ~5 files), fan out in ONE message; every spawn prompt carries goal + scope + what is already done + who consumes the result + acceptance.

What it does:

  • Receives the user prompt
  • Injects both lines via additionalContext — UserPromptSubmit cannot rewrite the prompt (updatedInput is silently dropped in CC 2.1.x)
  • Output is capped at 9000 chars (10K disk-spill safety, CC 2.1.174+)

When it fires: On every user input, including slash commands. Skipped only for a wrong hook_event_name, an empty prompt, and meta-replies that carry no task: yes/no/ok/thanks/continue/confirm-style answers, a bare number, or a single letter.


2. session-start.mjs

SessionStart additionalContext

Initializes the session, checks for brewcode/Claude updates, manages Plan Mode symlinks, and reports the active permission_mode in the system message for audit.

ParameterValue
EventSessionStart
ChanneladditionalContext
Timeout3000 ms

The hook reads permission_mode from the hook payload and appends it to systemMessage so every session records its trust level (default / plan / bypassPermissions). Output is capped at ~9000 chars (10K disk-spill safety, CC 2.1.174+).

Logic by session source:

SourceBehavior
initLog session_id, append permission_mode tag
resumeLog session_id, append permission_mode tag
clearCreate symlink LATEST.md -> newest plan

LATEST.md symlink:

  1. Checks ~/.claude/plans/ for .md files
  2. Picks the newest one (by mtime)
  3. If the file is less than 60 seconds old — creates .claude/plans/LATEST.md -> ~/.claude/plans/<newest>.md

🚀

Latest Release

Download, changelog, and installation instructions.

🔗

View on GitHub

Source code, README, and configuration files.