# Hooks and walls

> Hooks tell your code when a run starts, moves on or finishes. Walls are asked before the agent uses a tool, and can refuse it. Plus scripts only a person may run, and tabs of your own in the run panel.

Gates check the work. Hooks and walls are about the run: hooks are told what
is happening, walls decide what may happen.

## Hooks

A hook is a file droost runs when something happens in a run: post to chat,
update a dashboard, write a log.

```yaml
hooks:
  run_start:    droost/hooks/notify.php
  phase_begin:  droost/hooks/notify.php
  phase_end:    droost/hooks/notify.php
  run_complete: droost/hooks/notify.php
```

It gets JSON on standard input with `phase`, `run_id` and `work_item` (the
ticket, if there is one), and `DROOST_PROJECT_ROOT` in the environment.

```php
<?php
// droost/hooks/notify.php: one line per event, to a log.
$event = json_decode(stream_get_contents(STDIN), true);
file_put_contents(getenv('DROOST_PROJECT_ROOT') . '/droost/runs.log',
  date('c') . " {$event['run_id']} {$event['phase']}\n", FILE_APPEND);
echo "logged\n";
```

The first line it prints is shown to the agent. A hook can never stop or fail
a run.

## Walls

A wall is asked before the agent uses a tool whose name matches a pattern.
It lets the call through or refuses it, with a reason the agent sees.

```yaml
walls:
  tracker_writes: { tools: "^mcp__atlassian__(create|edit|transition)", run: droost/walls/tracker-writes.php }
```

It gets JSON on standard input with `tool_name`, `input` (the arguments) and
`project_root`. Exit `0` allows the call; exit `2` refuses it, and what you
print on standard error is the reason. A wall that crashes, is missing or
takes longer than 10 seconds refuses: it never says yes by accident.

```php
<?php
// droost/walls/tracker-writes.php: the agent may read the tracker, not write to it.
$call = json_decode(stream_get_contents(STDIN), true);
fwrite(STDERR, "Ask a person to make this change: {$call['tool_name']}\n");
exit(2);
```

A wall is asked about file edits, shell commands, MCP tools (`mcp__…`) and
the agent's questions to you: the calls the guard sees. It is not asked about
other tools, such as reading a file or fetching a web page. Walls work where
the guard does, which today means Claude Code.

## Scripts only a person may run

List a script under `operator_only:` and the guard refuses it from the agent,
and tells the agent to hand it to you:

```yaml
operator_only:
  - droost/bin/publish-release
```

## Panel tabs

Add a tab of your own to [the run panel](https://droost.org/docs/claude-plugin.md): a script that
prints JSON.

```yaml
panel:
  tabs:
    release: { title: Release, run: droost/panel/release.php }
```

It gets `project_root`, `tab` and `run` (the open run, or `null`) on standard
input, and prints one object:

```json
{ "badge": "2/3", "sections": [
  { "kind": "checklist", "title": "Before release", "items": [
    { "text": "Changelog written", "done": true },
    { "text": "Config exported", "done": true },
    { "text": "Translations updated", "done": false, "note": "fr missing" } ] } ] }
```

A section's `kind` is `text`, `markdown`, `fields` (`items: [{label, value}]`),
`checklist` (`items: [{text, done, note?}]`) or `list`
(`items: [{text, detail?, tag?, tone?}]`). A `.ts` or `.js` file runs under
Node. `droost workflow:panel --json` shows the whole panel, your tabs
included.
