AgentKit
DocsKitsCLI ReferenceDesktop App
BetaYou are reading docs for the beta channel (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:

EventWhat you can observe
PreToolUseProvides 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.
PostToolUseKeeps 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.
PreCompactCaptures enough workflow orientation to continue after context compaction and notes the compaction for the agent behavior linter.
SessionStartInitializes project and session context for the new session, and refreshes this project's background-auto-update liveness marker.
SubagentStartInitializes subagent state and supplies the relevant team context.
StopRefreshes 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.
SubagentStopRefreshes shared session state at the subagent boundary.
UserPromptSubmitSupplies 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.
SessionEndRemoves 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:

CallWarnsBlocks
Whole-file read with no offset, limit, or page rangeFile of 256 KB or moreFile of 2 MB or more
Search in content output mode with no head_limit of 100 or fewerAny other patternA pattern the scout check already treats as broad
Shell command that prints a whole file, such as cat or Get-ContentFile of 128 KB or moreFile of 512 KB or more
Unbounded log stream, recursive search or tree walk, or full Git history or diffAlways—

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.

  1. The agent asks. You approve or decline one access to the named path.
  2. 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.
  3. 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.
SurfaceClaude CodeCodexCursor
Hook event graphEmitted: 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 dependenciesDependencies 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 statuslineEmitted: 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:

  • PostToolUse matcher Agent|Task|TodoWrite|TodoRead, handler session-state.cjs, is fully dropped. Separate Stop and SubagentStop registrations remain.
  • Read|Write|Edit|Bash|PowerShell becomes Write|Edit|Bash, dropping Read and PowerShell while keeping privacy-block.cjs active for the supported atoms. Codex reads through MCP and local function tools are not guarded; see Sensitive-file approval.
  • Bash|PowerShell|Read becomes Bash, dropping PowerShell and Read while keeping scout-block.cjs active for Bash.
  • Bash|PowerShell|Read|Grep|Glob becomes Bash, dropping PowerShell, Read, Grep, and Glob while keeping context-firewall.cjs active for Bash.
  • Bash|PowerShell|WebFetch becomes Bash, dropping PowerShell and WebFetch while keeping secrets-inject.cjs active 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

  1. Read the installation disclosure. Check both the dropped and narrowed headings or their JSON fields before assuming the whole Hook graph failed.
  2. Confirm the target and scope. Project and user installs are separate, and the runtime can load a different copy than the one you expected.
  3. Run the target health check. Use ak doctor --adapter claude-code --json for Claude Code or ak doctor --adapter codex --json for Codex, and inspect the reported checks instead of assuming file presence means a Hook is active.
  4. Check the Windows path classification. An eligible in-home .cjs path can use a managed shim; unsupported external or unsafe path forms warn.
  5. 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.