3.0.0-beta.15). Features may change before the next stable release.Switch to stable →Hooks and runtime
Engineer Hooks and runtime support
Understand the Engineer Hook event graph, visible outcomes, and runtime-specific support boundaries.
Engineer Hooks add workflow automation around tool use, prompts, session boundaries, and handoffs. They can return guidance, request approval, stop an unsafe or overly broad operation, or update bounded workflow state. You do not invoke them as Skills or commands; the selected runtime calls the supported handlers when their events occur.
The resolved Engineer package contains 55 Hook package entries. That number is an artifact inventory, not 55 independent behaviors. It includes the Hook manifest, event handlers, shared support libraries, and Engineer-specific state and statusline helpers. One handler can participate in several events, while a support file may never run on its own.
Hooks supplement existing safeguards
Hook checks and reminders can reduce mistakes, but they do not replace runtime permissions, repository controls, secret management, code review, or your own approval. Review every requested action and the resulting changes.
Active event graph
The active graph is organized around outcomes rather than package file count:
| Event | What you can observe |
|---|---|
PreToolUse | Provides naming guidance for new files, checks sensitive-file access, stops overly broad or ignored-path scouting when the request falls outside the supported boundary, checks how much content a read, search, or shell command can return, and substitutes ${secret:NAME} references in shell commands and fetch URLs. A check may allow the request, return guidance, or ask for approval. |
PostToolUse | Keeps plan formatting coherent after edits and writes, refreshes task/session state after supported task operations, updates usage state after tool activity, and records bounded tool-activity totals for the agent behavior linter. |
PreCompact | Captures enough workflow orientation to continue after context compaction and notes the compaction for the agent behavior linter. |
SessionStart | Initializes project and session context for the new session, and refreshes this project's background-auto-update liveness marker. |
SubagentStart | Initializes subagent state and supplies the relevant team context. |
Stop | Refreshes session and usage state, and refreshes the auto-update liveness marker. When an active plan is bound, the plan reminder can return valid nonblocking JSON with continue and systemMessage; otherwise it exits silently. The agent behavior linter can add an advisory session report. |
SubagentStop | Refreshes shared session state at the subagent boundary. |
UserPromptSubmit | Supplies project-development guidance, checks for secret-like output risk, applies the simplify-before-shipping gate when relevant, refreshes usage state, and refreshes the auto-update liveness marker. |
SessionEnd | Removes this session's auto-update liveness marker. |
These outcomes support the Engineer workflow; they do not approve a plan, commit, merge, deploy, or release work for you.
Context firewall and agent behavior linter
The context-firewall Hook runs before Bash, PowerShell, Read, Grep,
and Glob calls and estimates how much content the call can return. It allows ordinary
calls, warns without blocking, or blocks the call and names a bounded
alternative:
| Call | Warns | Blocks |
|---|---|---|
Whole-file read with no offset, limit, or page range | File of 256 KB or more | File of 2 MB or more |
Search in content output mode with no head_limit of 100 or fewer | Any other pattern | A pattern the scout check already treats as broad |
Shell command that prints a whole file, such as cat or Get-Content | File of 128 KB or more | File of 512 KB or more |
| Unbounded log stream, recursive search or tree walk, or full Git history or diff | Always | — |
When the same warned operation reaches its third intervention in one session, the warning becomes a block. Each intervention is recorded in a bounded, per-session record with credential values redacted. The firewall fails open: an unexpected Hook error lets the call proceed.
The agent-behavior-linter Hook observes tool activity during a session and,
at Stop, can report patterns it proved from that activity: re-reading an
unchanged file, re-running a command that fails with the same error, repeated
broad searches, large tool output with no file changed, and a full test suite
run after a documentation-only change. Each finding names what was observed,
what to do instead, and the guiding principle in ak:context-engineering. The
linter keeps only bounded aggregates in private per-session state, never
blocks a tool call, and prints no report when it has no findings.
Both Hooks are on by default. To turn one off, set hooks.context-firewall or
hooks.agent-behavior-linter to false; see Managing Hook
preferences.
Sensitive-file approval
The privacy-block Hook runs before Read, Write, Edit, Bash, and
PowerShell calls and guards dotenv files such as .env and .env.local, credentials files,
secrets.yaml or secret.yml, .pem and .key files, and SSH private keys.
The first access to one of these paths is denied on every runtime and every
tool path, including paths named inside shell commands and Codex apply_patch
edits that arrive through the Edit and Write matchers. Shell inspection
also covers what a stdin shell (sh, bash -s, iex) runs from an earlier
pipeline segment, script text passed to [scriptblock]::Create or
InvokeScript, commands launched through Start-Process or xargs, and
wrappers that take an option value such as sudo -u, wsl -d, or
timeout -s. A dotenv name built by PowerShell string concatenation, matched
by a dot-leading glob, or spelled as an NTFS alternate data stream or Windows
8.3 short name is recognised too. Template files such
as .env.example, .env.sample, and .env.template stay allowed, and a
source expression such as process.env.API_KEY is not treated as a file path.
The denial names the operation (read, write, or edit) and tells the agent
to ask you through the runtime's ask-user capability: AskUserQuestion on
Claude Code, and request_user_input on Codex when it is available, otherwise
a plain chat question that ends the turn. Other runtimes receive the plain chat
question.
- The agent asks. You approve or decline one access to the named path.
- The agent retries once. After approval, it repeats the same call with
each protected path prefixed by
APPROVED:. The Hook removes the prefix before the tool runs, so the tool sees the real path. - The approval is consumed. It is bound to the session, the normalized path, and the operation, works for one call, and expires after 15 minutes. On Claude Code the approved retry still shows the native permission prompt with the exact call.
An APPROVED: prefix without a preceding denial, a second retry, an expired
approval, or a run with no session identity is denied. Non-interactive runs
therefore fail closed: nobody can answer, so the agent continues without the
file. When you decline, the agent is told to continue without the file and not
to reach it through another tool or command.
A guard against accidents, not a security boundary
The approval flow is model-cooperative. It reduces accidental reads and writes of secrets, but it does not defend against a malicious or compromised model. Keep credentials in a secret store and use runtime permissions as the enforcing control.
On Codex, the Read atom is dropped, so the Hook guards shell and edit paths
only; reads through MCP and local function tools are not guarded. AgentKit
reports this in the install output as part of the narrowed-matcher note. The
Hook is on by default; set hooks.privacy-block to false to turn it off, as
described in Managing Hook
preferences.
Secret references in tool calls
The secrets-inject Hook runs before Bash, PowerShell, and WebFetch
calls. When a shell command or fetch URL contains a ${secret:NAME} reference,
the Hook asks ak secrets resolve for the stored value and substitutes it in
memory, so the model can compose the call without seeing the credential. Only
Bash.command, PowerShell.command, and WebFetch.url are scanned; file contents and prompts are left untouched.
An ak secrets grant for the calling agent binary decides whether a
value is released, and a grant restricted with --allow-host also checks the
fetch destination.
Every failure blocks the tool call rather than passing the literal reference
through, including a missing grant, an invalid name, or a value with
shell-special characters bound for a Bash or PowerShell command. For a
PowerShell command only, a value that contains ,, @, or a typographic
quote is also refused, because PowerShell would read it as an array separator,
a splat, or a quote; pass such a value through an environment variable instead.
The Hook writes no copy of
the value, but the substituted input still reaches the tool process and
whatever the runtime records for an executed call, such as its transcript.
Treat a released value as present on the host and rotate it when needed.
Claude Code and Grok register the Hook for Bash|PowerShell|WebFetch, and Codex
narrows it to Bash. Cursor support for this Hook is not established in this release.
Background auto-update liveness
The session-heartbeat.cjs Hook is registered on SessionStart,
UserPromptSubmit, Stop, and SessionEnd. It writes a per-session marker
under the AgentKit home so the opt-in background auto-update
skips a project with a live agent session, and removes that marker at
SessionEnd. On SessionStart, it also prints a one-line notice for an
unacknowledged background-update result, acknowledged with
ak update --status --ack --json. The Hook never blocks a session and exits
0 even on its own failure.
Configured paths at session start
The SessionStart handler resolves project path preferences before exporting
the environment used by Engineer workflows. A relative paths.docs or
paths.plans value resolves under the session's project base. An absolute value
is exported verbatim as CK_DOCS_PATH or CK_PLANS_PATH, rather than being
joined to the project path. This export makes the configured locations
available to Skills; Go-native plan, journal, and dashboard surfaces separately
require a project-relative paths.plans and fall back to plans/ for an
absolute value.
Runtime projection
The labels below describe what becomes active:
- Emitted means AgentKit writes an active native artifact.
- Translated means AgentKit maps a supported behavior to the runtime's native event model.
- Dropped means the runtime cannot represent the behavior, so it is not active.
- Narrowed means the handlers remain active only for the representable subset of the original matcher.
- Sidecar-preserved means package material remains available for support or provenance but is not an active runtime feature by itself.
| Surface | Claude Code | Codex | Cursor |
|---|---|---|---|
| Hook event graph | Emitted: the declared graph uses Claude Code's native or plugin Hook surface. | Translated: representable events and matchers become Codex Hook registrations. Dropped: unsupported groups are omitted. Narrowed: supported matcher atoms stay active. Both outcomes are disclosed; installation can still succeed. | Translated: supported groups become Cursor Hook registrations. Source support does not prove identical behavior across every provider setup. |
| Handler dependencies | Dependencies required by active handlers are emitted; remaining support material is sidecar-preserved. | Dependencies required by translated handlers are emitted; remaining support material is sidecar-preserved. | Dependencies required by translated handlers are emitted; remaining support material is sidecar-preserved. |
| Engineer statusline | Emitted: active when AgentKit can install it without replacing a foreign statusline. | Dropped: unsupported. | Dropped: unsupported. |
The Engineer statusline is Claude Code only. Codex and Cursor do not gain a statusline merely because the package contains statusline support files.
Codex partial support
Codex has a smaller event and matcher model than Claude Code. Engineer projects one fully dropped group and four narrowed matchers:
PostToolUsematcherAgent|Task|TodoWrite|TodoRead, handlersession-state.cjs, is fully dropped. SeparateStopandSubagentStopregistrations remain.Read|Write|Edit|Bash|PowerShellbecomesWrite|Edit|Bash, droppingReadandPowerShellwhile keepingprivacy-block.cjsactive for the supported atoms. Codex reads through MCP and local function tools are not guarded; see Sensitive-file approval.Bash|PowerShell|ReadbecomesBash, droppingPowerShellandReadwhile keepingscout-block.cjsactive for Bash.Bash|PowerShell|Read|Grep|GlobbecomesBash, droppingPowerShell,Read,Grep, andGlobwhile keepingcontext-firewall.cjsactive for Bash.Bash|PowerShell|WebFetchbecomesBash, droppingPowerShellandWebFetchwhile keepingsecrets-inject.cjsactive for Bash.
The agent-behavior-linter.cjs registrations on PostToolUse, PreCompact,
and Stop are projected without narrowing.
A drop or narrow does not make installation fail. Human output lists them under
Hook groups not projected and Hook matchers narrowed
(some tool matches unsupported on this target). JSON uses the optional
hooksDropped, droppedHookSummaries, hookMatchersNarrowed, and
narrowedHookSummaries fields. Treat them as capability disclosure, not as
evidence that all Engineer automation is missing.
The plan reminder is exported only on wildcard Stop, not SubagentStop. With
no active plan it exits 0 with empty stdout. When relevant it emits JSON with
only allowed success fields, shaped as
{"continue":true,"systemMessage":"..."}, and remains fail-open.
Windows Hook launch support
On Windows, an eligible .cjs Hook inside the resolved Codex home can run from
an ordinary path containing spaces. AgentKit emits a managed sibling .cmd
shim and references that shim from the translated Hook configuration. The shim
is an AgentKit-owned output and participates in normal refresh and uninstall
handling.
This support is deliberately bounded. A spaced path outside the Codex home, a
path with cmd.exe metacharacters, a non-ASCII shim name, or unsafe/non-ASCII
baked arguments remains unsupported and produces a warning. Empty arguments get
a distinct warning; identical same-path shims deduplicate, while conflicting
content keeps the first shim and warns. Paths that do not need a shim continue
to use the direct command. Do not hand-edit generated shims or Hook configuration.
A wrapper-managed Codex home may link only the final hooks leaf to the
resolved user's canonical ~/.codex/hooks, using a POSIX symlink or a Windows
directory junction. AgentKit uses that canonical path for translated commands,
writes, rollback, and ownership. Linked parents and noncanonical targets remain
refused.
Diagnose the active Claude Code surfaces
ak doctor --adapter claude-code --json discovers enabled Claude Code Hook
surfaces by scope and delivery mode instead of treating package files as proof
that a Hook is active. For native project and user installs, the
hook_registration check compares the installed expectations with the active
settings.json registrations and verifies referenced entrypoints. For plugin
installs, it checks the active Hook manifest and its entrypoints. Hooks disabled
through AgentKit are excluded from the native expectations.
The hook_log check reads the log associated with each discovered native or
plugin surface. A new active surface with no log yet remains healthy; missing
registrations, manifests, or entrypoints are reported with their scope and
delivery mode. Use these results to identify the affected surface before
refreshing or reinstalling a Kit.
Ownership and lifecycle
Project and user-scope Hook registrations can coexist. For shared global Codex configuration, AgentKit merges registrations it owns while preserving user Hooks, foreign fields, and registrations owned by another Kit. A modified or conflicting entry is preserved or reported rather than silently overwritten.
Owned Hook metadata participates in rollback, and last-owner cleanup removes empty structures created by AgentKit without removing pre-existing or foreign structure. This is lifecycle hardening, not a security guarantee and not permission to delete arbitrary content from the shared Hooks configuration.
Refresh and uninstall operate on recorded AgentKit-owned material, not every Hook in the runtime. Review the operation preview and recovery snapshot before applying a lifecycle change. Do not delete a runtime home or the complete shared Hooks configuration as a repair step.
Troubleshoot safely
- Read the installation disclosure. Check both the dropped and narrowed headings or their JSON fields before assuming the whole Hook graph failed.
- Confirm the target and scope. Project and user installs are separate, and the runtime can load a different copy than the one you expected.
- Run the target health check. Use
ak doctor --adapter claude-code --jsonfor Claude Code orak doctor --adapter codex --jsonfor Codex, and inspect the reported checks instead of assuming file presence means a Hook is active. - Check the Windows path classification. An eligible in-home
.cjspath can use a managed shim; unsupported external or unsafe path forms warn. - Use the matching lifecycle route. Preview refresh or uninstall, preserve user-modified content, and inspect any recovery snapshot before applying a change.
To enable, disable, or restore one Hook's soft-preference without changing its installation, see Managing Hook preferences.
For exact install flags, see Installing kits
and the ak kit init reference. For
adapter-wide capability differences, see Runtime
adapters. If installation or discovery is
still unclear, continue with Kit installation
problems and Runtime cannot find a
Skill or Agent.
Hook groups not projected can be unsupported or deferred by the harness bridge. Read hooksDeferred separately from unsupported groups; deferred does not mean permanently unsupported.