harness-hook
Create and manage lifecycle hook entities in the agent-harness workspace. Covers canonical event authoring, provider projection (Claude Code, GitHub Copilot, OpenAI Codex CLI, Cursor), mode behavior, handler types, the full event/provider support matrix, copy-paste recipes, and common diagnostics.
Best use case
harness-hook is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Create and manage lifecycle hook entities in the agent-harness workspace. Covers canonical event authoring, provider projection (Claude Code, GitHub Copilot, OpenAI Codex CLI, Cursor), mode behavior, handler types, the full event/provider support matrix, copy-paste recipes, and common diagnostics.
Teams using harness-hook should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/harness-hook/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How harness-hook Compares
| Feature / Agent | harness-hook | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
Create and manage lifecycle hook entities in the agent-harness workspace. Covers canonical event authoring, provider projection (Claude Code, GitHub Copilot, OpenAI Codex CLI, Cursor), mode behavior, handler types, the full event/provider support matrix, copy-paste recipes, and common diagnostics.
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# /harness-hook — Lifecycle Hook Authoring
A `hook` entity defines shell commands that run at specific agent lifecycle points. The canonical source lives in `.harness/src/hooks/<hook-id>.json`. When you run `npx harness apply`, harness projects that single source into each enabled provider's native config format.
---
## Quick start
```bash
npx harness add hook <hook-id> # scaffold .harness/src/hooks/<hook-id>.json
# edit the file
npx harness apply # project to provider configs
```
---
## Canonical source shape
```json
{
"mode": "strict",
"events": {
"<canonical-event>": [
{
"type": "command",
"command": "node scripts/my-hook.js",
"timeoutSec": 15
}
]
}
}
```
### `mode` field
| Value | Behavior |
|---|---|
| `"strict"` (default) | Any unsupported provider/event/handler combination fails with a diagnostic (e.g., `HOOK_EVENT_UNSUPPORTED`). Use when you want to be sure every enabled provider receives the hook. |
| `"best_effort"` | Unsupported combinations are silently skipped. Use while prototyping multi-provider setups. |
Recommendation: start with `"best_effort"` during development, switch to `"strict"` once all providers are intentionally configured.
---
## Canonical event list and provider support
| Canonical event | Claude Code | GitHub Copilot | OpenAI Codex | Cursor |
|---|---|---|---|---|
| `session_start` | Yes (`SessionStart`) | Yes (`sessionStart`) | No | Yes (`sessionStart`) |
| `session_end` | Yes (`SessionEnd`) | Yes (`sessionEnd`) | No | Yes (`sessionEnd`) |
| `prompt_submit` | Yes (`UserPromptSubmit`) | Yes (`userPromptSubmitted`) | No | Yes (`beforeSubmitPrompt`) |
| `pre_tool_use` | Yes (`PreToolUse`) | Yes (`preToolUse`) | No | Yes (`preToolUse`) |
| `permission_request` | Yes (`PermissionRequest`) | No | No | No |
| `post_tool_use` | Yes (`PostToolUse`) | Yes (`postToolUse`) | No | Yes (`postToolUse`) |
| `post_tool_failure` | Yes (`PostToolUseFailure`) | No | No | Yes (`postToolUseFailure`) |
| `notification` | Yes (`Notification`) | No | No | No |
| `subagent_start` | Yes (`SubagentStart`) | No | No | Yes (`subagentStart`) |
| `subagent_stop` | Yes (`SubagentStop`) | No | No | Yes (`subagentStop`) |
| `stop` | Yes (`Stop`) | No | No | Yes (`stop`) |
| `stop_failure` | Yes (`StopFailure`) | No | No | No |
| `teammate_idle` | Yes (`TeammateIdle`) | No | No | No |
| `task_completed` | Yes (`TaskCompleted`) | No | No | No |
| `instructions_loaded` | Yes (`InstructionsLoaded`) | No | No | No |
| `config_change` | Yes (`ConfigChange`) | No | No | No |
| `worktree_create` | Yes (`WorktreeCreate`) | No | No | No |
| `worktree_remove` | Yes (`WorktreeRemove`) | No | No | No |
| `pre_compact` | Yes (`PreCompact`) | No | No | Yes (`preCompact`) |
| `post_compact` | Yes (`PostCompact`) | No | No | No |
| `elicitation` | Yes (`Elicitation`) | No | No | No |
| `elicitation_result` | Yes (`ElicitationResult`) | No | No | No |
| `error` | No | Yes (`errorOccurred`) | No | No |
| `turn_complete` | No | No | Yes (`notify`) | No |
---
## Provider projection details
### Claude Code
- **Output file:** `.claude/settings.json` (key: `hooks`)
- **Event names:** PascalCase (e.g., `pre_tool_use` → `PreToolUse`)
- **Handler types:** `command` only (harness canonical). Claude Code natively supports `command`, `http`, `prompt`, and `agent` handler types, but the harness canonical format only supports `command`.
- **Matcher:** supported on most events — filters by tool name, session source, exit reason, etc. Specify `"matcher"` on the handler; harness groups handlers with the same matcher under one entry.
- **Output shape:**
```json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{ "type": "command", "command": "python3 scripts/check_bash_policy.py" }
]
}
]
}
}
```
- **Blocking events:** `PreToolUse`, `PermissionRequest`, `UserPromptSubmit`, `Stop`, `SubagentStop`, `TeammateIdle`, `TaskCompleted`, `ConfigChange`, `Elicitation`, `ElicitationResult`, `WorktreeCreate` — a handler exiting with code 2 blocks/denies the action.
- **Official docs:** https://code.claude.com/docs/en/hooks
### GitHub Copilot
- **Output file:** `.github/hooks/harness.generated.json` (top-level `version: 1`)
- **Event names:** camelCase (e.g., `pre_tool_use` → `preToolUse`, `prompt_submit` → `userPromptSubmitted`)
- **Supported canonical events:** `session_start`, `session_end`, `prompt_submit`, `pre_tool_use`, `post_tool_use`, `error`
- **Handler types:** `command` only
- **Matcher:** NOT supported — fails in `"strict"` mode, silently ignored in `"best_effort"` mode
- **Output shape:**
```json
{
"version": 1,
"hooks": {
"preToolUse": [
{
"type": "command",
"bash": "node scripts/pre_tool.js",
"powershell": "node scripts/pre_tool.js",
"timeoutSec": 15
}
]
}
}
```
- **Official docs:** https://docs.github.com/en/copilot/reference/hooks-configuration
### OpenAI Codex CLI
- **Output file:** `.codex/config.toml` (key: `notify = [...]`)
- **Supported canonical events:** `turn_complete` only
- **Handler types:** both `notify` and `command` handlers are accepted — both normalize to a TOML notify command array
- **Matcher:** NOT supported
- **Normalization rules:**
- `notify` handler: `command` field used directly; string values are wrapped as `["sh", "-lc", "<command>"]`; arrays pass through unchanged
- `command` handler: first available field among `command`, `bash`, `linux`, `osx`, `powershell`, `windows` is selected and wrapped the same way
- **Conflict rule:** only one notify command is allowed across all enabled hook entities. If two hooks produce different notify commands, `apply` fails with `HOOK_NOTIFY_CONFLICT`.
- **Output shape:**
```toml
notify = ["python3", "scripts/on_turn_complete.py"]
```
- **Official docs:** https://developers.openai.com/codex/config-reference
### Cursor
- **Output file:** `.cursor/hooks.json` (key: `hooks`, top-level `version: 1`)
- **Event names:** camelCase (e.g., `pre_tool_use` → `preToolUse`, `prompt_submit` → `beforeSubmitPrompt`)
- **Supported canonical events:** `session_start`, `session_end`, `prompt_submit`, `pre_tool_use`, `post_tool_use`, `post_tool_failure`, `subagent_start`, `subagent_stop`, `pre_compact`, `stop`
- **Handler types:** `command` only
- **Matcher:** supported — filters by tool name, subagent type, or command text depending on the event
- **Limitations:** `cwd` and `env` fields on command handlers are NOT supported (harness errors if present)
- **Hook execution types:** Cursor supports both command-based hooks (shell scripts) and prompt-based hooks (LLM-evaluated natural language conditions), but harness only projects command-based hooks.
- **Per-script options:** `timeout` (seconds), `loop_limit` (for `stop`/`subagentStop`), `failClosed` (block action on hook failure)
- **Output shape:**
```json
{
"version": 1,
"hooks": {
"preToolUse": [
{
"command": "./scripts/validate-tool.sh",
"matcher": "Shell|Read|Write"
}
]
}
}
```
- **Additional Cursor-native events (no canonical equivalent):** `beforeShellExecution`, `afterShellExecution`, `beforeMCPExecution`, `afterMCPExecution`, `beforeReadFile`, `afterFileEdit`, `afterAgentResponse`, `afterAgentThought`, `beforeTabFileRead`, `afterTabFileEdit`. These are not projectable from harness canonical events; use them directly in `.cursor/hooks.json` if needed.
- **Configuration locations:** Project (`.cursor/hooks.json`), user (`~/.cursor/hooks.json`), enterprise (system-wide), team (cloud-distributed)
- **Priority order:** Enterprise → Team → Project → User
- **Exit code behavior:** Exit code `0` = success, exit code `2` = block/deny, other codes = fail-open (unless `failClosed: true`)
- **Official docs:** https://docs.cursor.com/agent/hooks
---
## Handler type reference
### `command` handler
```json
{
"type": "command",
"command": "node scripts/hook.js",
"bash": "node scripts/hook.js",
"linux": "node scripts/hook.js",
"osx": "node scripts/hook.js",
"windows": "node scripts\\hook.js",
"powershell": "node scripts\\hook.js",
"matcher": "Bash",
"cwd": ".",
"env": { "MY_VAR": "value" },
"timeoutSec": 30
}
```
- At least one of `command`, `bash`, `linux`, `osx`, `windows`, `powershell` is required.
- `matcher` is Claude-only; ignored/errors for Copilot (strict mode error), not applicable for Codex.
- `env` values must all be strings.
- `timeoutSec` (or `timeout`) must be a positive number.
### `notify` handler (Codex only)
```json
{
"type": "notify",
"event": "agent-turn-complete",
"command": ["python3", "scripts/on_turn_complete.py"]
}
```
- `event` defaults to `"agent-turn-complete"` when omitted — it is the only supported value.
- `command` must be a non-empty string or string array.
- `matcher` is not supported on `notify` handlers.
---
## Copy-paste recipes
### 1) Claude + Copilot pre-tool guard
Use `"strict"` mode because `pre_tool_use` is supported by both providers.
```json
{
"mode": "strict",
"events": {
"pre_tool_use": [
{
"type": "command",
"bash": "node scripts/hook-pre-tool.js",
"powershell": "node scripts/hook-pre-tool.js",
"timeoutSec": 15
}
]
}
}
```
Outputs:
- Claude: `.claude/settings.json` `hooks.PreToolUse`
- Copilot: `.github/hooks/harness.generated.json` `hooks.preToolUse`
### 2) Claude matcher-based tool policy
Applies only when the `Bash` tool is invoked. Copilot does not support matcher — use a separate hook file or `"best_effort"` mode if Copilot is also enabled.
```json
{
"mode": "strict",
"events": {
"pre_tool_use": [
{
"type": "command",
"matcher": "Bash",
"command": "python3 scripts/check_bash_policy.py"
}
]
}
}
```
Claude output fragment:
```json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{ "type": "command", "command": "python3 scripts/check_bash_policy.py" }
]
}
]
}
}
```
### 3) Codex turn-complete notification
```json
{
"mode": "strict",
"events": {
"turn_complete": [
{
"type": "notify",
"command": ["python3", "scripts/on_turn_complete.py"]
}
]
}
}
```
Codex output in `.codex/config.toml`:
```toml
notify = ["python3", "scripts/on_turn_complete.py"]
```
### 4) Cross-provider best_effort hook (all three providers)
Covers `pre_tool_use` for Claude/Copilot and `turn_complete` for Codex in one file. Unsupported combinations are skipped rather than failing.
```json
{
"mode": "best_effort",
"events": {
"pre_tool_use": [
{
"type": "command",
"bash": "node scripts/pre_tool.js",
"powershell": "node scripts/pre_tool.js"
}
],
"turn_complete": [
{
"type": "notify",
"command": ["python3", "scripts/on_turn_complete.py"]
}
]
}
}
```
Behavior:
- Claude: receives `PreToolUse`; `turn_complete` is skipped.
- Copilot: receives `preToolUse`; `turn_complete` is skipped.
- Codex: receives `notify`; `pre_tool_use` is skipped.
---
## Common diagnostics
| Code | Cause |
|---|---|
| `HOOK_JSON_INVALID` | Source file is not valid JSON |
| `HOOK_MODE_INVALID` | `mode` is not `"strict"` or `"best_effort"` |
| `HOOK_EVENTS_INVALID` | `events` key is missing or not an object |
| `HOOK_EVENT_UNKNOWN` | Event key is not in the canonical event list |
| `HOOK_HANDLER_TYPE_INVALID` | Handler `type` is not `"command"` or `"notify"` |
| `HOOK_COMMAND_MISSING` | `command` handler has no command field |
| `HOOK_TIMEOUT_INVALID` | `timeoutSec`/`timeout` is not a positive number |
| `HOOK_ENV_INVALID` | `env` is not a string-to-string map |
| `HOOK_NOTIFY_EVENT_INVALID` | `notify` handler `event` is not `"agent-turn-complete"` |
| `HOOK_NOTIFY_COMMAND_INVALID` | `notify` handler `command` is empty or wrong type |
| `HOOK_EVENT_UNSUPPORTED` | Event is not supported by the target provider (strict mode) |
| `HOOK_NOTIFY_CONFLICT` | Two Codex hooks produce different notify commands |
| `HOOK_TARGET_CONFLICT` | Multiple hook entities for one provider resolve to different output paths |
---
## CLI commands
```bash
npx harness add hook <hook-id> # scaffold .harness/src/hooks/<hook-id>.json
npx harness apply # project all hooks to provider configs
npx harness plan # dry-run: preview operations without writing
npx harness remove hook <hook-id> # remove hook entity and source file
npx harness remove hook <hook-id> --no-delete-source # remove entity but keep source
npx harness validate # validate manifest, ownership, and constraints
```
---
## Output path overrides
To send a hook entity to a custom path for a specific provider, create a sidecar file:
`.harness/src/hooks/<hook-id>.overrides.<provider>.yaml`
```yaml
version: 1
targetPath: ".github/hooks/security.generated.json"
```
If multiple hook entities for the same provider resolve to different target paths, `apply` fails with `HOOK_TARGET_CONFLICT` (or `CODEX_CONFIG_TARGET_CONFLICT` for Codex).
---
## Official documentation
- Claude Code hooks: https://code.claude.com/docs/en/hooks
- GitHub Copilot hooks configuration: https://docs.github.com/en/copilot/reference/hooks-configuration
- GitHub Copilot about hooks: https://docs.github.com/en/copilot/concepts/agents/coding-agent/about-hooks
- OpenAI Codex config reference: https://developers.openai.com/codex/config-reference
- Cursor hooks: https://docs.cursor.com/agent/hooksRelated Skills
harness-subagent
Create and manage subagent entities in the agent-harness workspace. Invoke this skill when a user wants to define, scaffold, apply, or remove a subagent across Claude Code, OpenAI Codex, or GitHub Copilot.
harness-skill
Create and manage skill entities in the agent-harness workspace. Use when the user wants to add a new skill, understand how skills work across providers, or generate provider skill artifacts from a canonical source.
harness-settings
Configure provider-specific settings (permissions, model, features) for Claude Code, OpenAI Codex CLI, and GitHub Copilot using the harness settings entity.
harness-prompt
Create and manage the system prompt entity in agent-harness, generating CLAUDE.md, AGENTS.md, and copilot-instructions.md from a single canonical source.
harness-mcp
Create and manage MCP server config entities in the agent-harness workspace.
npm-workflow-release
Release agent-harness packages to npm and verify publication. Use when the user asks to cut a release, push a missing release tag, or troubleshoot why a version was not published.
academic-figure-drawing-harness
Codex drawing harness integration. Triggers: 繪圖, draw, figure, chart, plot, mermaid, SVG, Gemini, graph, 生成圖表.
zotero-keeper-harness
Cline harness for Zotero Keeper and the bundled VS Code extension. Triggers: zotero keeper, zotero mcp, full check, release checklist, workflow, vsix, Cline.
pubmed-search-mcp-harness
Cline harness for PubMed Search MCP. Triggers: pubmed search, literature search, unified_search, pipeline, fulltext, release checklist, Cline.
asset-aware-mcp-harness
Cline harness for this repo (rules + workflows + checks). Triggers: cline harness, full check, release checklist, workflow, 文檔工作流, DFM, citation-ready.
enable-feedback-hooks
Enable feedback capture hooks for the current project
hooks
Use when generating hooks, headlines, titles, and scroll-stopping openers for content. Also use when analyzing viral posts, Reels, TikToks, YouTube Shorts, or successful social examples to extract reusable hook patterns and improve hook guidance.