Skip to content

Executable Hooks

Licensed commands, with one deliberate exception. Every CortexBuild command except auth requires a valid licence — except cortexbuild 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-zero PreToolUse exit 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-start still surfaces the build-currency check either way (it needs no licence — see below); stop records 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.