Executable Hooks¶
Licensed commands, with one deliberate exception. Every CortexBuild command except
authrequires a valid licence — exceptcortexbuild hook. Nothing chooses to run a hook; the AI host invokes it on every session, every turn, every edit, licensed or not, and a host cannot act on "license required" the way a person can: Copilot reads a non-2 non-zeroPreToolUseexit as deny, so a gated hook would silently block every edit in an unlicensed or lapsed repo; other hosts would surface a hook error, or forward raw stderr to the agent, on every turn. A hook that breaks the host is worse than one that does nothing.session-startstill surfaces the build-currency check either way (it needs no licence — see below);stoprecords nothing when unlicensed rather than fail.
In plain English
CortexBuild has always shipped prose guidance (.claude/hooks/*.md,
.github/hooks/*.md) that an agent reads. This page is about something
different: small executable configs that the AI host itself runs — no
agent cooperation required — at session start, when a turn ends, and
before an Edit/Write tool call. One command,
cortexbuild hook <event> --host <host>, answers every host's own JSON
stdin / stdout / exit-code contract.
What each hook does¶
| Event | What it does |
|---|---|
session-start |
Surfaces CortexBuild <installed> is installed; <latest> is the latest release plus up to three release highlights, when the install is behind. Silent when current. |
stop |
Records one telemetry event for the turn (rule-telemetry-emission), reusing whatever cortexbuild telemetry mark accumulated. De-duplicates against a manual cortexbuild telemetry record call in the same session — you will never see two events for one turn. |
pre-edit |
Denies an Edit/Write under legacy/** (only when the repo has a legacy/ directory), naming rule-modernization-invariants. Allows everything else silently. |
Every rendered hook invokes the same command, so there is exactly one place that knows the per-host stdin/stdout shape:
cortexbuild hook session-start --host claude-code
cortexbuild hook stop --host codex
cortexbuild hook pre-edit --host gemini
Per-host coverage¶
The table below is a summary; the full matrix includes context-injection and deny formats per host.
| Host | Rendered into | session-start | stop | pre-edit | Default |
|---|---|---|---|---|---|
| Claude Code | .claude/settings.json (hooks) |
Full | Full | Full | on |
| Codex CLI | .codex/hooks.json + .codex/config.toml |
Full | Full | Full | on |
| Gemini CLI / Antigravity | .gemini/settings.json (hooks) |
Full | Full | Full | on (moot until the gemini target itself is enabled) |
| Copilot CLI + cloud agent | .github/hooks/cortexbuild.json |
Full | Full | Full | on |
| Copilot VS Code agent mode | same file, but VS Code ignores matcher — the hook filters on tool_name itself |
Full | Full | Full (self-filtered) | on |
| Kiro | .kiro/hooks/cortexbuild-*.json (4 files: SessionStart, AgentSpawn, AgentStop, PreToolUse) |
Full | Full | Full | on |
| Cursor | .cursor/hooks.json (sessionStart, stop only) |
Full | Full | none — Cursor has no pre-edit hook at all | off, opt in |
| Windsurf | .windsurf/hooks.json (pre_write_code only) |
none — no session-start hook exists | partial (post_cascade_response, no session id) |
Full | off, opt in |
| opencode | opencode.json permission.edit map |
— | — | Full, but WITHOUT ever invoking cortexbuild hook — opencode has no shell-command hook mechanism at all |
off, opt in, and only materialises the file when a legacy/ directory exists |
| Cline | none rendered | — | — | — | not supported (see below) |
| Aider | none | — | — | — | no hook mechanism exists |
"Partial" means exactly what the table cell says — Cursor cannot deny an
edit before it happens (no beforeFileEdit hook exists in its config schema
today); Windsurf has no session-start trigger at all. Turning exec_hooks: true
on for either does not silently degrade anything — it renders whatever that
host actually supports and nothing more.
Cline is not rendered even though its hook mechanism
(.clinerules/hooks/<HookName> executables) could express pre-edit: it is
script-file-based rather than JSON-config-based (macOS/Linux only, and must be
enabled in settings), a different rendering shape than every other host here.
It is not added as a registry target that would accept exec_hooks: true and
render nothing -- a name the registry loader accepts must actually do
something, or it is worse than rejecting it.
Opt in / opt out per host¶
# .cortexbuild/registry.yaml (repo-local overlay, additive over the
# packaged registry)
render_targets:
cursor:
enabled: true
exec_hooks: true # off by default; this repo wants it anyway
windsurf:
enabled: true # windsurf has no other render surface — enabling
exec_hooks: true # the target and enabling hooks go together
claude_code:
exec_hooks: false # this repo wants CLAUDE.md but not the hooks
Run cortexbuild render after changing this, then cortexbuild doctor — a
host with exec_hooks: true but a missing rendered file (edited the registry,
forgot to render) is reported there.
The launcher fallback¶
A rendered hook invokes the cortexbuild console script, which is not
guaranteed to be on PATH inside every host's own shell (a sandboxed tool
shell, a bare devcontainer). Every rendered command therefore tries the real
launcher first and falls back to running the package as a module:
command -v cortexbuild >/dev/null 2>&1 && cortexbuild hook <event> --host <host> \
|| python3 -m cortexbuild_core hook <event> --host <host>
Windows equivalent (for a hook file that runs under PowerShell, e.g.
Copilot's powershell variant):
if (Get-Command cortexbuild -ErrorAction SilentlyContinue) {
cortexbuild hook <event> --host <host>
} else {
python -m cortexbuild_core hook <event> --host <host>
}
Merge safety¶
.claude/settings.json, .codex/hooks.json, .gemini/settings.json,
.cursor/hooks.json, and .windsurf/hooks.json may already carry your own
hooks (or, for the first three, unrelated settings). cortexbuild render
never overwrites these files wholesale: it tags every entry it owns with
"_cortexbuild": "managed" and only ever replaces or removes entries carrying
that tag. Every foreign key and every hand-written hook entry survives
byte-for-byte, in place, across every re-render. cortexbuild eject reverses
this the same way — it strips only the tagged entries and leaves the rest of
the file exactly as it was.
If you use Claude Code¶
CortexBuild is also packaged as a Claude Code plugin — the same three hooks, packaged with a small set of skills and agents, no repo render required. A public marketplace listing is planned; ask your CortexBuild contact for the current install method.