Skip to main content
Concept · Explanation

Hook relay

The senkani-hook binary is a zero-dependency Mach-O that Claude Code (and other MCP-capable agents) runs before every Read/Bash/Grep/Write/Edit tool call. It decides: allow, allow-with-rewrite, or deny.

What it intercepts

Registered during senkani init with matcher Read|Bash|Grep|Write|Edit. Claude Code fires PreToolUse hook → the hook binary runs (< 5 ms active, < 1 ms passthrough) → the hook returns a decision.

The three outcomes

  1. Allow + pass through. The tool runs as requested. Common for Write, Edit, and mutating Bash.
  2. Allow + rewrite. The tool still runs, but output is post-processed (secret redaction, ANSI strip, filter rules). Common for Read and Bash.
  3. Deny with a reason. The tool does not run. The reason string goes to the model — often with a cached answer inline. Common for re-reads of unchanged files, ls/pwd, redundant build re-runs.

Why a separate binary

Claude Code's hook protocol spawns a fresh process per tool call. Swift apps with full MLX linkage take tens of milliseconds to start; that's too slow to sit in front of every tool call. So the hook is a minimal binary (Sources/Hook), zero MLX dependency, IPC-connects to the long-running MCP server via a Unix-domain socket.

The shared HookRelay library

Sources/HookRelay/ is imported by both senkani-hook (the standalone binary) and the main app's --hook mode (useful during testing). Same decision logic, same socket wire format.

SENKANI_PANE_ID gating

MCP tools are only active in Senkani-managed terminals — the app injects SENKANI_PANE_ID=<id> when it spawns a pane's shell. Non-Senkani terminals never see senkani tools even if the MCP server is running globally. This prevents pollution and keeps the trust boundary well-defined.

Socket-auth handshake

With SENKANI_SOCKET_AUTH=on, every hook connection sends a length-prefixed handshake frame matching the token at ~/.senkani/.token (mode 0600, rotated on every server start). Ambient same-UID attackers can no longer talk to the sockets; they'd need to read the token file too.