> ## Documentation Index
> Fetch the complete documentation index at: https://docs.neo.projectdiscovery.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Hooks

> Standing rules Neo cannot talk itself out of, enforced on every matching call

An instruction is a preference Neo weighs against everything else happening in a task, and can reasonably move past if circumstances call for it. A **hook** is different: it is a standing rule you set once, and it applies the same way every time a matching situation comes up, without Neo needing to remember it or decide whether to follow it.

That difference matters most exactly where instructions are weakest: a long task where earlier context has scrolled away, an ambiguous request, or text in a repository that tries to talk Neo into something. A hook is checked in the execution path, not read out of the prompt, so none of that reaches it.

<CardGroup cols={2}>
  <Card title="Keep Neo out of somewhere" icon="ban">
    Block reads or writes under a directory, against a host, or on a particular tool — whichever route it would take.
  </Card>

  <Card title="Ask me first" icon="hand">
    Hold a call until you approve it, for the operations where being wrong is expensive.
  </Card>

  <Card title="Give every task a standing fact" icon="note-sticky">
    Inject context at the start of every run, so nobody has to remember to mention it.
  </Card>

  <Card title="Do not finish until…" icon="flag-checkered">
    Send Neo back when it tries to end a task before something you require has happened.
  </Card>
</CardGroup>

## How a rule runs

Take one concrete call: Neo runs `cat vendor/config.yaml` through `run_command`.

```mermaid theme={"system"}
flowchart LR
    A["run_command: cat vendor/config.yaml"] --> B[PreToolUse fires]
    B --> C{Matcher match?}
    C -->|No| D[Call proceeds]
    C -->|Yes| E["Action: deny"]
    E --> F[Reason returned]
    F --> G[Neo works around it]
```

<Steps>
  <Step title="The event fires">
    Neo calls `run_command` with `cat vendor/config.yaml`. This raises a **PreToolUse** event carrying the tool name and its arguments.
  </Step>

  <Step title="The matcher is checked">
    Every enabled PreToolUse hook is checked against the call. This one matches on **path**:

    ```json theme={"system"}
    { "path": "vendor/" }
    ```
  </Step>

  <Step title="The action is applied">
    The matcher matched, so its action runs:

    ```json theme={"system"}
    { "type": "deny", "reason": "vendor/ is off limits." }
    ```
  </Step>

  <Step title="Neo continues">
    The reason comes back as the tool's own result, not as a crash. Neo works around it: it tries a different approach, asks you, or moves on to something else.
  </Step>
</Steps>

## Creating a rule

Ask Neo in plain language. It proposes the rule, and **every create, enable, disable, and delete waits for your explicit approval** before it takes effect — a standing rule is not something an agent gets to set for you on its own say-so.

<Note>
  Listing your rules is the one action that does not need approval. Reading policy changes nothing.
</Note>

## Events

| Event                | When it fires                                       | What an action can do                                                 |
| :------------------- | :-------------------------------------------------- | :-------------------------------------------------------------------- |
| `PreToolUse`         | Before a tool runs                                  | Deny the call, or send it back to be retried with different arguments |
| `PostToolUse`        | After a tool succeeds                               | Attach context to the result                                          |
| `PostToolUseFailure` | After a tool fails                                  | Attach context to the result                                          |
| `PermissionRequest`  | When Neo checks whether a call needs human approval | Require approval before the call runs                                 |
| `SessionStart`       | When a task begins                                  | Add standing context to the run                                       |
| `SubagentStop`       | When a subagent is about to hand back control       | Send it back with a reason                                            |
| `Stop`               | When Neo is about to finish the task                | Send it back with a reason                                            |

## Choosing a matcher

The matcher decides whether a rule holds. This is where rules most often go wrong: one that looks right can quietly cover nothing, or cover one route while leaving three others open.

The same action reaches a file through several tools, under several argument names — `read_file` carries `path`, `write_file` carries `file_path`, and `run_command` hides it inside `command`.

| Field    | Matches on                                                                     | Use it for                                                          |
| :------- | :----------------------------------------------------------------------------- | :------------------------------------------------------------------ |
| `path`   | Every string in the call, whatever the argument is named, relative or absolute | Anything about a file or directory. One rule covers every route in. |
| `tool`   | Exact name or glob, e.g. `github_*`                                            | Narrowing a match you already have                                  |
| `source` | `chat`, `slack`, or `github`                                                   | Restricting a rule to where the task came from                      |
| `args`   | One named field, via `equals`, `contains`, `glob`, `in`, or `is_secret`        | A genuine field test — a host, a flag, a credential-shaped value    |

<Warning>
  **"Never touch X" belongs on `path`, not on `args`.** An `args` condition matches one named field, so a rule written that way catches `read_file` and silently lets `run_command` and `write_file` through. A `tool` matcher on its own has the same problem in reverse — it leaves every other tool open.
</Warning>

A `path` ending in a slash means that directory **and everything beneath it**. `vendor/` covers `vendor`, `vendor/lib/x.go`, and `rm -rf vendor` alike. Write `vendor/**` if you want the contents but not the directory itself.

An absent matcher field matches anything; an empty matcher (`{}`) matches everything within its scope.

## PreToolUse

Good for blocking a call before it runs, or sending it back to be retried with different arguments.

You say:

> never touch anything under the vendor directory

Rule created:

```json theme={"system"}
{ "event": "PreToolUse", "scope": "user", "matcher": { "path": "vendor/" }, "action": { "type": "deny", "reason": "vendor/ is off limits." } }
```

## PostToolUse

Good for attaching guidance to a result Neo just produced.

You say:

> after any dependency install, remind me to check the lockfile diff

Rule created:

```json theme={"system"}
{ "event": "PostToolUse", "scope": "user", "matcher": { "args": [{ "path": "command", "op": "contains", "value": "install" }] }, "action": { "type": "additional_context", "text": "Check the lockfile diff before committing." } }
```

## PostToolUseFailure

Good for adding guidance when a call fails, the same way PostToolUse does for a success.

You say:

> when a deploy fails, remind me to check rollout status before retrying

Rule created:

```json theme={"system"}
{ "event": "PostToolUseFailure", "scope": "user", "matcher": { "args": [{ "path": "command", "op": "contains", "value": "deploy" }] }, "action": { "type": "additional_context", "text": "Check rollout status before retrying." } }
```

## PermissionRequest

Good for holding a call until a person approves it.

You say:

> always ask before writing to production

Rule created:

```json theme={"system"}
{ "event": "PermissionRequest", "scope": "user", "matcher": { "args": [{ "path": "target.host", "op": "contains", "value": "prod" }] }, "action": { "type": "ask", "reason": "Writes to production need sign-off." } }
```

```mermaid theme={"system"}
flowchart LR
    A[Matching call] --> B[PermissionRequest fires]
    B --> C[Task suspends]
    C --> D{Your decision}
    D -->|Approve| E[Call runs, task resumes]
    D -->|Reject| F[Neo continues without it]
```

The task waits durably: it survives a restart, and resuming carries the same policy it started with.

A permission check can only decide whether to ask, so it takes neither `deny` nor `allow`. To block a call outright, use a `deny` on PreToolUse. To stop being asked about something, narrow the rule's matcher rather than adding a second rule to waive it — a waiver cannot override an approval another rule already requires.

## SessionStart

Good for giving every task in a workspace, or for a user, a standing fact to start from.

You say:

> remember that our payments and billing services are internet facing

Rule created:

```json theme={"system"}
{ "event": "SessionStart", "scope": "org", "matcher": {}, "action": { "type": "additional_context", "text": "Payments and billing services are internet facing." } }
```

## SubagentStop

Good for sending a subagent back to finish something before it hands control back.

You say:

> don't let a subagent stop without writing a summary of what changed

Rule created:

```json theme={"system"}
{ "event": "SubagentStop", "scope": "user", "matcher": {}, "action": { "type": "deny", "reason": "Write a summary of what changed before finishing.", "maxDenials": 1 } }
```

## Stop

Good for holding the task open until something you require has happened.

You say:

> don't let a task finish without running the test suite

Rule created:

```json theme={"system"}
{ "event": "Stop", "scope": "org", "matcher": {}, "action": { "type": "deny", "reason": "Run the test suite before finishing.", "maxDenials": 2 } }
```

Neo is told who set the rule, so it treats a send-back as your policy rather than as text that leaked in from somewhere — and it can still explain why it disagrees instead of looping.

## Scopes

| Scope      | Who sets it                            | Who it affects                      |
| :--------- | :------------------------------------- | :---------------------------------- |
| **org**    | A workspace administrator              | Everyone in the workspace           |
| **repo**   | A workspace administrator              | Everyone working on that repository |
| **user**   | Any user, for themselves               | Only that person's tasks            |
| **thread** | Any user, for the current conversation | Only that conversation              |

Scopes are additive: every matching hook is evaluated, and any single deny wins. A narrower scope can never loosen a rule set at a broader one, so a user or thread rule cannot reopen something an org rule denies.

Repository rules are enforced like any other, but cannot be created or changed by asking Neo during a task. The repository a task is working on is context, not proof of write access to it — so ask for a user or org rule instead.

## Actions

| Action               | Does                                                       | Meaningful on                                 |
| :------------------- | :--------------------------------------------------------- | :-------------------------------------------- |
| `deny`               | Blocks the call and returns a reason                       | PreToolUse, Stop, SubagentStop                |
| `allow`              | Lets the call through without further checks               | PreToolUse, Stop, SubagentStop                |
| `ask`                | Requires human approval before the call runs               | PermissionRequest                             |
| `additional_context` | Adds text to the run, or to a result                       | SessionStart, PostToolUse, PostToolUseFailure |
| `update_input`       | Sends the call back to be retried with corrected arguments | PreToolUse                                    |
| `judge`              | Defers the decision to a configured policy evaluator       | PreToolUse, Stop, SubagentStop                |

An action paired with an event that does not read it is refused when you create it, and ignored if one is already stored — a rule that could not decide anything should never look like it is enforcing something.

<AccordionGroup>
  <Accordion title="How update_input actually behaves">
    It does not silently rewrite arguments. The call is blocked and Neo is handed the corrected arguments with an instruction to call the tool again using them; that retry then goes through validation and approval like any other call.

    This matters for approval: arguments are approved *before* a rule could rewrite them, so a silent rewrite would execute something you never saw. The cost is one extra model round trip, and Neo may choose to do something else instead of retrying.
  </Accordion>

  <Accordion title="What judge does today">
    `judge` hands the decision to an external policy evaluator. Until one is configured it does not evaluate: an ordinary rule using it is skipped, and a **locked** organisation rule using it fails closed — it blocks, rather than quietly permitting something an administrator meant to gate.
  </Accordion>

  <Accordion title="Limits that keep a rule from running away">
    * A `Stop` or `SubagentStop` denial is bounded by `maxDenials`, and by a global ceiling across all rules, so no rule can hang a task indefinitely. Each run gets its own budget.
    * Added context is capped in total, shared between SessionStart and post-tool text, so policy cannot crowd out the task.
    * A tool argument too large or too deeply nested to inspect fully is reported rather than silently skipped.
    * Patterns are matched without regular expressions, so no rule can be written that is expensive to evaluate.
  </Accordion>
</AccordionGroup>

## Managing rules

Ask Neo directly, the same way you created the rule.

* "what rules do I have set?" lists them, scoped to what you can see.
* "disable the vendor rule" turns one off without deleting it.
* "delete that rule" removes it for good.

Neo shows you what it found before acting on it, and every change waits for your approval.

## When a rule does not fire

<AccordionGroup>
  <Accordion title="It matched one tool but not another">
    The matcher is probably an `args` condition on a field only one tool uses. Move it to `path`, which is checked against every string in the call.
  </Accordion>

  <Accordion title="It never matches anything">
    A `path` with no wildcard matches only that exact string. Name the directory with a trailing slash (`vendor/`) to cover it and everything beneath it.
  </Accordion>

  <Accordion title="It was created but does nothing">
    Check the event and action pair. Actions only work on the events that read them — an `ask` belongs on PermissionRequest, `additional_context` on SessionStart or the post-tool events.
  </Accordion>

  <Accordion title="The rule is right but a broader one wins">
    Scopes are additive and deny wins. An organisation rule cannot be loosened by a user or thread rule.
  </Accordion>
</AccordionGroup>

## Good to know

* A denied tool call does not fail the task. Neo reads the reason back as the tool's own result and works around it.
* Hooks apply inside subagents too, not just the main agent, and survive suspend, approval, and resume.
* A rule set for `chat` does not apply to a task that arrived from Slack or GitHub unless you say so — use `source` deliberately.
