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

# Hooks

> Run custom logic at key points in QwenWork workflows.

<style
  dangerouslySetInnerHTML={{ __html: `
/* qwenwork-navigation-tabs-override */
#adoc-layout { --topbar-height: var(--topbar-row1-height) !important; }
#navbar[data-has-tabs="true"] > div:has(.nav-tabs) { display: none !important; }
` }}
/>

Hooks let you insert custom logic at key points during QwenWork execution without changing application code. By editing a JSON configuration file, you can:

- Block dangerous operations before a tool runs.
- Run lint automatically after a file is written.
- Show a desktop notification when the Agent finishes.

Unlike prompt instructions, Hooks are deterministic: whenever a configured event fires, the script runs.

## Quick Start

The following example blocks commands containing `rm -rf`.

### 1. Create the script

```bash
mkdir -p ~/.qwenwork/hooks
cat > ~/.qwenwork/hooks/block-rm.sh << 'EOF'
#!/bin/bash
input=$(cat)
command=$(echo "$input" | jq -r '.tool_input.command')

if echo "$command" | grep -q 'rm -rf'; then
  echo "Dangerous command blocked: $command" >&2
  exit 2
fi

exit 0
EOF
chmod +x ~/.qwenwork/hooks/block-rm.sh
```

### 2. Add the configuration

Add the following to `~/.qwenwork/settings.json`:

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "~/.qwenwork/hooks/block-rm.sh"
          }
        ]
      }
    ]
  }
}
```

### 3. Verify the result

Open QwenWork and ask the Agent to execute a command containing `rm -rf`. The Hook blocks the command and returns the error message to the Agent.

## Configure Hooks

### Configuration file

| Location                    | Scope      | Description                                                  |
| --------------------------- | ---------- | ------------------------------------------------------------ |
| `~/.qwenwork/settings.json` | User level | Personal configuration that applies to all QwenWork sessions |

<Note>
  Hot reload is not currently supported. Restart QwenWork after changing the configuration file.
</Note>

### Configuration format

```json
{
  "hooks": {
    "EventName": [
      {
        "matcher": "match condition",
        "hooks": [
          {
            "type": "command",
            "command": "command to execute",
            "timeout": 60
          }
        ]
      }
    ]
  }
}
```

| Field     | Required | Description                                            |
| --------- | -------- | ------------------------------------------------------ |
| `type`    | Yes      | Must be `"command"`                                    |
| `command` | Yes      | Shell command to execute                               |
| `timeout` | No       | Timeout in seconds; the default is 60                  |
| `matcher` | No       | Match condition; omit it to match every event instance |

An event can contain multiple matcher groups, and each group can contain multiple Hook commands.

### Matcher rules

| Pattern                  | Meaning              | Example                                 |
| ------------------------ | -------------------- | --------------------------------------- |
| Leave empty or `"*"`     | Match all            | Run for every tool                      |
| Exact value              | Exact match          | `"Bash"` matches only the Bash tool     |
| Values separated by `\   | `                    | Match several values                    | `"Write \ | Edit"` matches Write or Edit |
| Regular expression       | Pattern match        | `"mcp__.*"` matches MCP tools           |

## Write a Hook Script

A Hook script receives JSON through **stdin** and controls behavior using its exit code and stdout. This section describes the input and output shared by all events. Event-specific fields are documented under [Hook Events](#hook-events).

<Note>
  QwenWork does not inject environment variables into Hook scripts. Read the session ID, working directory, tool information, and other event data from the stdin JSON payload.
</Note>

### Input

All events include the following common fields:

| Field             | Description                  |
| ----------------- | ---------------------------- |
| `session_id`      | Current session ID           |
| `cwd`             | Current working directory    |
| `hook_event_name` | Name of the event that fired |

Different events add their own fields to this payload. Use `jq` to parse the input:

```bash
#!/bin/bash
input=$(cat)
tool_name=$(echo "$input" | jq -r '.tool_name')
```

### Output

Exit code `0` indicates success. Exit code `2` blocks supported events and injects stderr into the conversation. Other exit codes are treated as non-blocking errors.

When a script exits with `0`, supported events can parse JSON from stdout for more precise control. Stdout is ignored when the exit code is not `0`.

## Hook Events

### SessionStart

Fires when a session starts.

**Matcher:** session source

| Matcher value | Scenario                         |
| ------------- | -------------------------------- |
| `startup`     | Start a new session              |
| `resume`      | Resume an existing session       |
| `compact`     | Context compaction has completed |

```json
{
  "source": "startup",
  "model": "Auto"
}
```

### SessionEnd

Fires when a session ends.

**Matcher:** end reason

- `prompt_input_exit`: The user exits input, for example with Ctrl+D.
- `other`: Any other reason.

```json
{
  "reason": "prompt_input_exit"
}
```

### UserPromptSubmit

Fires after the user submits a prompt and before the Agent starts processing it.

```json
{
  "prompt": "Write a sorting function"
}
```

### PreToolUse

Fires before a tool runs and can block the tool call.

**Matcher:** tool name, such as `Bash`, `Write`, `Edit`, `Read`, `Glob`, or `Grep`. MCP tools use names such as `mcp__server__tool`.

```json
{
  "tool_name": "Bash",
  "tool_input": {"command": "rm -rf /tmp/build"},
  "tool_use_id": "toolu_01ABC123"
}
```

To block the tool, exit with code `2`. Stderr is returned to the Agent as an error.

### PostToolUse

Fires after a tool completes successfully.

**Matcher:** tool name

```json
{
  "tool_name": "Write",
  "tool_input": {"file_path": "/path/to/file.ts", "content": "..."},
  "tool_response": "File written successfully",
  "tool_use_id": "toolu_01ABC123"
}
```

### PostToolUseFailure

Fires after a tool fails.

**Matcher:** tool name

```json
{
  "tool_name": "Bash",
  "tool_input": {"command": "npm test"},
  "tool_use_id": "toolu_01ABC123",
  "error": "Command exited with non-zero status code 1",
  "is_interrupt": false
}
```

### Stop

Fires when the main Agent finishes a response and has no pending tool calls. The Hook can prevent the Agent from stopping so it continues working.

To prevent the Agent from stopping, exit with code `2`. Stderr is injected into the conversation as a message and the Agent continues.

### SubagentStart / SubagentStop

Fire when a sub-agent starts or finishes. Like `Stop`, `SubagentStop` can prevent a sub-agent from stopping.

**Matcher:** Agent type

```json
{
  "agent_id": "a1b2c3d4",
  "agent_type": "task"
}
```

### PreCompact

Fires before context compaction.

**Matcher:** trigger type

| Matcher value | Scenario                                                       |
| ------------- | -------------------------------------------------------------- |
| `manual`      | The user runs `/compact` manually                              |
| `auto`        | The context window is full and compaction starts automatically |

```json
{
  "trigger": "manual",
  "custom_instructions": "Preserve all tool results"
}
```

### Notification

Fires for notifications such as permission requests and completed tasks.

**Matcher:** notification type

- `permission`: Permission request notification.
- `result`: Agent result notification.

```json
{
  "message": "Agent is requesting permission to run: rm -rf node_modules",
  "title": "Permission Required",
  "notification_type": "permission"
}
```

### PermissionRequest

Fires when a tool needs user authorization.

**Matcher:** tool name

```json
{
  "tool_name": "Bash",
  "tool_input": {"command": "rm -rf node_modules"}
}
```

## Use Cases

### Desktop notifications

Show a desktop notification when the Agent completes a task or needs authorization.

Create `~/.qwenwork/hooks/notify.sh` on macOS:

```bash
#!/bin/bash
input=$(cat)
message=$(echo "$input" | jq -r '.message')

if echo "$message" | grep -q "^Agent"; then
  osascript -e 'display notification "Task completed" with title "QwenWork"'
else
  osascript -e 'display notification "Authorization required" with title "QwenWork"'
fi

exit 0
```

Configure the Hook:

```json
{
  "hooks": {
    "Notification": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "~/.qwenwork/hooks/notify.sh"
          }
        ]
      }
    ]
  }
}
```

### Run lint after file changes

Run lint automatically whenever the Agent writes or edits a file.

Create `${project}/.qwenwork/hooks/auto-lint.sh`:

```bash
#!/bin/bash
input=$(cat)
file_path=$(echo "$input" | jq -r '.tool_input.file_path')

case "$file_path" in
  *.js|*.ts|*.jsx|*.tsx)
    npx eslint "$file_path" --fix 2>/dev/null
    ;;
esac

exit 0
```

Configure the `PostToolUse` event with matcher `Write|Edit` and command `.qwenwork/hooks/auto-lint.sh`.

### Ask the Agent to continue

When the Agent tries to stop, check for unfinished work and inject a message that tells it to continue.

Create `~/.qwenwork/hooks/check-continue.sh`:

```bash
#!/bin/bash
if [ -n "$(git status --porcelain 2>/dev/null)" ]; then
  echo "Uncommitted changes detected. Complete the git commit." >&2
  exit 2
fi

exit 0
```

Configure the `Stop` event with command `~/.qwenwork/hooks/check-continue.sh`.
