Claude Code is designing Function Hooks: in-process middleware, no ambient filesystem or network, every side effect through $. Whether or not it ships, a handler that receives its I/O is testable and portable; one that owns it is neither. Watch issue: #3917.
import { execSync } from 'node:child_process';
export function guard(input, ctx) {
const branch = execSync('git branch --show-current');
...
}export function guard(input, deps) {
const branch = deps.git('branch --show-current');
...
}
// deps today: execSync-backed. Under FH: $.process-backed.$ node scripts/fh-ready-check.mjs fh-ready-check: OK (111 grandfathered, 0 new) $ node scripts/fh-ready-check.mjs # after adding a handler that imports node:fs fh-ready-check: 1 NEW handler(s) own their I/O (node:fs, node:child_process, or fetch). src/pretool/zz-probe/probe.ts
CC 2.1.258 truncates additionalContext at 8,000 characters or 200 lines, mid-word, with no report. Measured on this tree: 93 context handlers, largest static template 709 chars, 37 build context from files at runtime. The new test fails at 4,000 static chars and lists the 37.
Full analysis, mocks and decision record: platform docs/playgrounds/dev/2026-09-04-cc-function-hooks-summary.html.