> For the complete documentation index, see [llms.txt](https://docs.observal.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.observal.io/reference/hooks-spec.md).

# Hooks specification

The schema Observal uses for hook definitions -- both the registry hook type (`observal registry hook`) and hooks wired into harness configs by `observal agent pull` / `observal doctor patch`.

Current version: `HOOKS_SPEC_VERSION = "5"` (see `observal_cli/hooks_spec.py`).

## Where hooks live

Two distinct things share the name "hook":

1. **Registry hooks**: packaged, versioned hook definitions in the Observal registry. Install them via `observal registry hook install`.
2. **harness hooks**: entries in `~/.claude/settings.json`, `.kiro/agents/<name>.json`, etc. These are written by `observal agent pull` and `observal doctor patch`.

Both use the same event vocabulary.

## Events

| Event              | When it fires                             |
| ------------------ | ----------------------------------------- |
| `SessionStart`     | New harness session begins                |
| `Stop`             | Session ends                              |
| `SubagentStop`     | Sub-agent session ends (Claude Code only) |
| `UserPromptSubmit` | User submits a prompt                     |
| `PreToolUse`       | Before a tool call                        |
| `PostToolUse`      | After a tool call (with result)           |
| `Notification`     | harness surfaces a notification           |

Source: `observal_cli/constants.py:VALID_HOOK_EVENTS`.

## Handler types

| Type      | Payload                           | Used by                                              |
| --------- | --------------------------------- | ---------------------------------------------------- |
| `command` | Shell command with templated args | Kiro (shell hooks only), Claude Code (local scripts) |
| `http`    | URL + method + headers + body     | Claude Code (native HTTP hooks)                      |

## Execution modes

| Mode       | Semantics                                             |
| ---------- | ----------------------------------------------------- |
| `async`    | Fire and forget - harness doesn't wait                |
| `sync`     | harness waits for handler to return before continuing |
| `blocking` | Handler can veto the event (e.g. block a tool call)   |

Source: `observal_cli/constants.py:VALID_HOOK_EXECUTION_MODES`.

## Scopes

| Scope     | Effect                                |
| --------- | ------------------------------------- |
| `agent`   | Applies only to one agent             |
| `session` | Applies for the duration of a session |
| `global`  | Applies across everything             |

## Metadata marker

Observal writes a `_observal` key into hook matcher groups so subsequent runs of `doctor patch` / `pull` can find and update only Observal-managed hooks without stomping on user-authored ones.

```json
{
  "_observal": {
    "version": "5",
    "source": "observal-scan",
    "installed_at": "2026-04-21T14:30:55Z"
  },
  "PreToolUse": [ ... ]
}
```

Older installs (pre-metadata) are detected with a fallback heuristic.

## Claude Code: native HTTP hook example

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "_observal": { "version": "5", "source": "observal-pull" },
        "matcher": "*",
        "type": "http",
        "url": "http://localhost/api/v1/telemetry/hooks",
        "method": "POST",
        "headers": {
          "Authorization": "Bearer ${OBSERVAL_API_KEY}",
          "Content-Type": "application/json"
        }
      }
    ]
  }
}
```

## Kiro: shell-command hook example

Kiro doesn't support native HTTP hooks, so Observal uses `curl`:

```json
{
  "name": "my-agent",
  "hooks": {
    "agentSpawn":       "curl -s -X POST http://localhost/api/v1/telemetry/hooks -H 'Authorization: Bearer $OBSERVAL_API_KEY' -H 'Content-Type: application/json' -d @-",
    "userPromptSubmit": "curl -s -X POST http://localhost/api/v1/telemetry/hooks ...",
    "preToolUse":       "curl -s -X POST http://localhost/api/v1/telemetry/hooks ...",
    "postToolUse":      "curl -s -X POST http://localhost/api/v1/telemetry/hooks ...",
    "stop":             "curl -s -X POST http://localhost/api/v1/telemetry/hooks ..."
  }
}
```

## Event name mapping (Claude Code ↔ Kiro)

Kiro uses camelCase / lowercase event names; Claude Code uses PascalCase. Observal maps between them.

| Claude Code        | Kiro               |
| ------------------ | ------------------ |
| `SessionStart`     | `agentSpawn`       |
| `Stop`             | `stop`             |
| `SubagentStop`     | *(no equivalent)*  |
| `UserPromptSubmit` | `userPromptSubmit` |
| `PreToolUse`       | `preToolUse`       |
| `PostToolUse`      | `postToolUse`      |
| `Notification`     | *(no equivalent)*  |

## Registry hook payload shape

When submitting a hook to the registry (`observal registry hook submit`):

```json
{
  "name": "pretooluse-logger",
  "description": "Logs every tool call to a local file",
  "event": "PreToolUse",
  "handler_type": "command",
  "command": "echo \"$TOOL_NAME $(date)\" >> ~/.observal/tool-log.txt",
  "execution_mode": "async",
  "scope": "agent",
  "harness": ["claude-code", "kiro"]
}
```

Each field is validated server-side against the lists in `observal_cli/constants.py` (mirrored from `observal-server/schemas/constants.py`).

## Source of truth

* `observal_cli/hooks_spec.py`: version, metadata marker, spec shape
* `observal_cli/constants.py`: valid events, handler types, execution modes, scopes
* `observal-server/schemas/constants.py`: server-side mirror

A sync test (`tests/test_constants_sync.py`) ensures CLI and server stay in lockstep.

## Related

* [`observal registry hook`](/cli-reference/registry.md)
* [Session tracking and reconciliation](/core-concepts/session-tracking.md)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.observal.io/reference/hooks-spec.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
