Reference
Warming the hook binaries
A hook binary that has never been executed still owes macOS its first-run work: the system assesses the executable's signature before it runs, that assessment reaches Apple over the network, and XProtect scans the image. The first run of a freshly installed hook binary can take minutes, while every run after it takes a few milliseconds. An engine that killed a hook at a fixed number of seconds would therefore refuse a finished turn on a cold cache with a timeout — a refusal nobody wrote.
The engine no longer counts seconds. A hook is waited for and its own answer
decides the event, because the number was a guess at how long an answer may
take and the hook killed at it reported a failure it never had. Nothing in the
registry carries a timeout, and no run is killed for being slow:
a slow gate is found by what it costs, not replaced by a verdict it never
gave. Waiting is not free, though. The OMP adapter sets no limit of its own,
but OMP does: a tool-call handler that has not answered within OMP's
extensionHandlers.toolCallTimeoutMs setting (thirty seconds unless
the operator set it) is refused by OMP as
Extension …/omp-shared-hooks.js timed out after …ms, and the tool
does not run. A cold binary inside a live event costs that event. So the
first-run cost is paid deliberately, outside the event.
tama hooks release runs every binary the registry names once,
right after installing, and prints what each one cost. A binary that is missing
or unrunnable does not stop the activation, since the release it replaces is no
better, but the command then ends with
the release is active, but the hook binaries named above are unusable; a
live event refuses on each until `tama hooks warm` reports it warm and
exits 1, so a release whose hooks cannot run is not read as a clean
install. Run the same step on its own when binaries arrived another way, or to
see where this machine stands:
tama hooks warm
tama hooks warm --only <hook-id>[,<hook-id>…] --json
Each line carries the state, the measured milliseconds, the hook id and the
path: warm ran and exited, missing is a registry
entry with no binary at that path, and unrunnable is a binary that
is there and could not be started or waited for (no execute permission, a
format the system does not run); standard error names it with the system's
cause, as <id> is unrunnable (cannot start: Permission denied
…): <path>. The summary names the binary that cost the
most, which is the one to look at. The command exits non-zero when a binary is
missing or unrunnable, because a live event would have nothing to run.
--json prints one document with the same rows, each with its
cause (null for a binary that ran or is missing), plus
warmed and unusable counts and the
slowest hook with its slowestMs; a report that cannot
be encoded is refused with its cause. An id the registry does not name is
refused by name and exits 2.
The run is inert: each binary receives an empty JSON object on standard
input, which every hook reads as "no input, decide nothing", and
HOME points at a scratch directory inside the checkout's ignored
build tree, so a hook that writes state writes it there. No consent variable is
required, because nothing about the machine's policy changes.
Tama Desktop carries the same step: the Hooks screen shows
a Hook binaries panel with a Warm hook binaries
button, the count it warmed, and a row for every binary that was slow or could
not run, an unrunnable one with the system's cause beside its path. Both
surfaces run the same warm pass — the window through
tama request hooks/warm — and read the same document.