Skip to content
Blume
Esc
↑↓navigate↵open⌘Jpreview

Updated

On this page

Hooks

Skillset hooks define native and adaptive source, events, actions, attachments, provider rendering, and activation limits.

Feature Feature status claude codex cursor
adaptive-hooks implemented transformed degraded degraded
future-companion-source-pointers planned planned planned planned
plugin-hooks implemented pass_through pass_through pass_through
runtime-context implemented transformed transformed transformed

Support vocabulary: Feature Reference

Hooks are rendered definitions only. Skillset never installs, trusts, enables, or mutates user-level Claude, Codex, or Cursor configuration as a side effect of build, check, diff, import, init, or create.

Skillset supports two hook source styles:

  • Native aggregate hooks preserve provider-native plugin hook files at hooks/hooks.json.
  • Adaptive hook units are reusable source units under hooks/<name>.json or hooks/<name>/hook.json. They attach to plugins, skills, or project agents and render only where Skillset can preserve the intended scope.

Skillset-Owned Project SessionStart

compile.session_start_hook controls one advisory project hook, separate from plugin and adaptive hooks. on composes npx skillset hooks run session-start into .claude/settings.json and .codex/hooks.json; off removes only Skillset’s command and its settings-entry lock claim. auto enables it only when every enabled project’s skill output root is gitignored, including Cursor’s skill root when enabled. Cursor has no verified project hook destination, so it receives no SessionStart entry.

The runner reports stale generated paths, suggests previewing with npx skillset build and writing with npx skillset build --yes, and points at the use-skillset skill; it never rebuilds, installs, or trusts the hook. The committed SessionStart entry uses npx so it resolves for every contributor who checks out the repo, including those without a global install. The post-tool-use and stop snippets from skillset hooks print --agent-runtime stay bare skillset because they are reviewed and pasted per environment; set SKILLSET_HOOK_COMMAND there when the CLI is not on PATH. Skillset identifies its entry by command, leaving other settings keys and hook entries intact. See Settings for field-level ownership and the old Claude local-settings migration guard.

Native Aggregate Hooks

The canonical plugin hook source is <source-root>/plugins/<plugin>/hooks/hooks.json. The source root is .skillset/. Plugin-root hooks.json is rejected; put hook definitions under hooks/hooks.json.

Hook source is JSON with an aggregate hooks event map. Event entries may include a matcher, statusMessage, and a hooks array whose handlers declare a non-empty type plus handler-specific fields such as command, prompt, agent, timeout, and async. The active source contract is generated from @skillset/schema; see schema reference and hook examples for the current field set.

Use native aggregate source when the hook file is already provider-shaped or intentionally provider-specific. Use adaptive hook units when the hook behavior should attach to a plugin, skill, or project agent and Skillset should render only destinations that preserve that scope.

hooks/hooks.json is a destination-specific native aggregate source. It is not a universal sink for portable hook behavior, and it cannot be combined with adaptive hook units for the same generated plugin hook destination.

Imported provider-native hook files stay native by default. Skillset should only lift native hook material into adaptive hook units when provider capability facts prove the event, matcher, handler, scope, and runtime behavior can be preserved faithfully. Otherwise, native source remains the honest representation.

Target Rendering

Source Claude output Codex output Cursor output Status Notes
hooks/hooks.json hooks/hooks.json hooks/hooks.json hooks/hooks.json target_native / implemented Destination-specific native aggregate source rendered with a top-level hooks object.
root hooks.json n/a n/a n/a unsupported Move the file to hooks/hooks.json.
Future hooks.source n/a n/a n/a planned No feature-key source pointer is implemented.

Adaptive Hook Units

Adaptive hook units are Skillset-authored source. They let authors define hook behavior once, attach it to the source unit where it belongs, and have Skillset render only provider/destination combinations that preserve the intended scope and runtime behavior.

Adaptive hooks are separate from workflow hook guardrails. Guardrails such as skillset hooks print and skillset hooks run stop help local Git hooks or reviewed agent-runtime snippets call Skillset checks. Adaptive hooks compile source-authored runtime hook definitions into provider-native hook surfaces.

Source Model

Source Meaning
hooks/<name>.json Flat adaptive hook unit.
hooks/<name>/hook.json Directory adaptive hook unit with hook-local sidecars.
hooks/<name>/<name>.json Directory adaptive hook unit with a manifest named after the directory.
hooks/hooks.json Reserved native aggregate escape hatch, not an adaptive hook unit.

Directory hook units must resolve to the directory name. If both hook.json and <name>.json exist in the same hook directory, Skillset fails with an ambiguous manifest diagnostic.

Native aggregate mode and adaptive mode do not merge for the same generated destination. If hooks/hooks.json and adaptive hook units would both write a provider plugin hooks/hooks.json, Skillset fails and asks the author to choose one source model.

Scripts

Adaptive hook run.script references have two source-backed forms:

Reference Source proof
./check.js Hook-local script beside a directory hook unit such as hooks/<name>/hook.json or hooks/<name>/<name>.json.
{{scripts.dir}}/check.js Shared script under the owner source directory’s scripts/ folder. Root hooks use <source-root>/scripts/; plugin hooks use <plugin>/scripts/; skill-local hooks use the skill directory’s scripts/; project-agent-local hooks use the sibling agent directory’s scripts/.

Flat hook units such as hooks/<name>.json cannot use ./... hook-local scripts because they do not own a private sidecar directory. Use a directory hook unit for colocated sidecars or {{scripts.dir}}/... for owner-level shared scripts.

Plugin-level adaptive hook rendering copies referenced scripts into the generated plugin root and rewrites commands to provider runtime roots: $CLAUDE_PLUGIN_ROOT for Claude plugin hooks and $PLUGIN_ROOT for Codex and Cursor plugin hooks.

Runtime Context

Adaptive hook units can choose how much Skillset-normalized runtime context to pass to their command:

{
  "events": ["Stop"],
  "context": {
    "strategy": "inline",
    "env": ["provider", "hook.event", "session.id"]
  },
  "run": {
    "command": "node ./session-summary.js"
  }
}

context.strategy supports:

Strategy Behavior
inline Prefixes the generated command with requested SKILLSET_* environment assignments.
none Passes no Skillset-normalized context. Provider-native environment remains available.
toolkit Renders through skillset hooks context, backed by the shared private @skillset/toolkit/runtime context model.

Inline fields are deliberately small:

Field Generated variable
provider SKILLSET_PROVIDER (claude, codex, or cursor)
hook.event SKILLSET_HOOK_EVENT
session.id SKILLSET_SESSION_ID, sourced from ${CLAUDE_SESSION_ID:-}, ${CODEX_SESSION_ID:-}, or ${CURSOR_SESSION_ID:-}

Omitting context is equivalent to context.strategy: none, so existing hooks keep their command text unchanged.

For context.strategy: toolkit, generated hooks call the installed Skillset command:

skillset hooks context --event <event> --format env --context-fields <field,...>

The helper prints shell export statements for the requested SKILLSET_* values and preserves provider-native environment access for the hook command. It does not erase target-native variables such as CLAUDE_SESSION_ID, CODEX_SESSION_ID, or CURSOR_SESSION_ID; scripts that need provider-specific data can still read the raw environment deliberately. For Cursor hooks, session.id resolves in deterministic order from an explicit SKILLSET_SESSION_ID, then the JSON stdin conversation_id, then CURSOR_SESSION_ID as a compatibility fallback. Claude and Codex continue to use their provider session environment variables after an explicit Skillset override.

Generated hooks and out-of-repo scripts should use skillset hooks context, the public command shipped by the skillset package. Repo-local tools that already depend on the internal workspace package can import the typed runtime surface directly:

import { renderRuntimeContext } from "@skillset/toolkit/runtime";

const output = await renderRuntimeContext({
  env: process.env,
  event: "Stop",
  fields: ["provider", "hook.event", "session.id"],
  format: "json",
});

process.stdout.write(output);

Shell hooks can evaluate env output:

eval "$(skillset hooks context --event Stop --format env --context-fields provider,hook.event,session.id)"
printf '%s\n' "$SKILLSET_PROVIDER:$SKILLSET_HOOK_EVENT"

Python hooks can consume JSON output without shell evaluation:

import json
import subprocess

context = json.loads(subprocess.check_output([
    "skillset",
    "hooks",
    "context",
    "--event",
    "Stop",
    "--format",
    "json",
]))
print(context["provider"])

Attachments

Hook definitions are reusable. Source units attach named hooks through event-keyed frontmatter or config:

hooks:
  PreToolUse:
    - source-change-guard
  Stop:
    - hook: shell-policy
      match:
        tool: [Bash]
      status: Checking shell changes
      providers: [claude, codex]
  auto:
    - session-metadata

Plugin-level attachments live in the plugin skillset.yaml next to plugin metadata:

skillset:
  name: source-guard
hooks:
  PreToolUse:
    - hook: shell-policy
      match: Bash
      status: Checking shell command
      providers: [claude, codex]

hooks.auto expands from the hook definition’s declared events. Attachment-level matchers can narrow a definition but cannot broaden its declared event, matcher, handler, or provider support.

Resolution is nearest-first for named hook references:

  1. Skill-local hooks.
  2. Agent-local hooks.
  3. Plugin-local hooks.
  4. Workspace hooks.

Nearest-first resolution only chooses named source definitions. It does not override provider runtime behavior.

Current Render Support

The implemented render slices support plugin-level command/script hooks, plugin-level run.env shell assignments, and Claude frontmatter command hooks. When a plugin attachment resolves to a provider-compatible adaptive hook, Skillset writes provider-native hooks/hooks.json into generated provider plugin outputs and declares hooks in the plugin manifest. When a Claude skill-local or project-agent-local attachment resolves to a command hook, Skillset writes provider-native hooks frontmatter for the generated skill or agent.

Skill-local and project-agent-local attachments currently render only to Claude frontmatter. Plugin-shipped agent frontmatter hooks are not implemented; use a plugin-level hook or provider-native aggregate source until that destination has a faithful render path. Scope Codex- or Cursor-incompatible skill or agent attachments with providers: [claude] when the intent is Claude-only. If Codex or Cursor is enabled and an attachment cannot be faithfully rendered, build, diff, and output checks surface an adaptive-hooks unsupported:error render result instead of writing a broader plugin or project hook.

Provider blocks are closed, typed overrides for events, match, context, and run. An absent field inherits the portable unit; a supplied field replaces that complete semantic unit; match: null and context: null clear only those values. An override cannot enable a provider excluded by the unit’s providers list. Skillset resolves this effective definition once per target before validating attachments, scripts, and target capabilities.

Unsupported cases include Codex and Cursor skill/agent no-faithful-destination cases, Claude-only plugin events, Codex-ignored matchers, context.includeRaw until raw-context semantics exist, ambiguous hooks that define both run.command and run.script, unsupported run.args/run.cwd fields, frontmatter run.env fields, and frontmatter run.script path-proof gaps.

Provider Reference

Use skillset lookup hooks --events --compat <target> for provider event, matcher, finite matcher value, handler, and support facts. Use skillset lookup hooks adaptive --fields --schema --examples for the adaptive hook unit source contract. Use skillset lookup hooks toolkit --field context.env --values --compat <target> for the normalized runtime context field matrix.

Use skillset new hook <name> --event <event> --command <command> --attach <source-unit> to preview an attached adaptive hook. Repeat --event for a multi-event unit, use --script <path> instead of --command for schema-supported script actions, and repeat --provider only to narrow the registry-derived compatible provider set. Attachment selectors use existing source identities such as plugin:guard, skill:writer, plugin.guard.skill:writer, and agent:reviewer; the selected owner determines canonical hook placement and receives a hooks.auto attachment. Flat hooks/<name>.json is the deterministic default, while a local ./ script reference selects hooks/<name>/hook.json. Preview is the default, --yes writes both the hook and attachment, collisions fail, and no build runs implicitly.

Bare interactive skillset new exposes the same operation: searchable attachment and event choices come from the live source graph and registry, event choices show compatible providers, unsupported attachment destinations show classifier-derived reasons, and explicit flags skip their matching prompts. The final preview and default-No confirmation still come from the shared scaffold report.

Provider details are intentionally registry-backed instead of duplicated here. Claude has the broader hook surface; Codex and Cursor support are narrower and currently center on plugin-level command handlers. Cursor uses provider-native lower-camel event names. skillset lookup hooks --events --compat claude,codex,cursor is the preferred way to inspect the current matrix. Skillset keeps provider-specific validation explicit so incompatible hooks fail visibly instead of being silently widened or dropped.

Provider docs checked: 2026-06-25.

Diagnostics

  • Reject plugin-root hooks.json and hook files that are not JSON objects.
  • Validate hook file shape with the shared @skillset/schema contract used by Workbench and schema artifacts.
  • Validate provider-native hook events and handler types through packages/core/src/hook-capabilities.ts.
  • Reject unsupported events, missing handler types, and handler types that the selected provider does not run for that event.
  • Reject async command handlers for providers that parse but skip them.

Provenance

Hook definitions are generated plugin files. Plugin lock hashes include hook source content through plugin output hashes; hooks are not activation state.

Evidence

Schema, capability, attachment, adapter, and runtime-context tests cover both source models and every generated support claim. Use the generated hook schema and example, the Hook Guardrails maintainer reference, and Build Versus Activation for the separate runtime authority boundary.

Last updated on

Was this page helpful?