Skip to main content
APP Capabilities

Hooks

Run custom logic at key points in QwenWork workflows.

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

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:
{
  "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

LocationScopeDescription
~/.qwenwork/settings.jsonUser levelPersonal configuration that applies to all QwenWork sessions
Hot reload is not currently supported. Restart QwenWork after changing the configuration file.

Configuration format

{
  "hooks": {
    "EventName": [
      {
        "matcher": "match condition",
        "hooks": [
          {
            "type": "command",
            "command": "command to execute",
            "timeout": 60
          }
        ]
      }
    ]
  }
}
FieldRequiredDescription
typeYesMust be "command"
commandYesShell command to execute
timeoutNoTimeout in seconds; the default is 60
matcherNoMatch 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

PatternMeaningExample
Leave empty or "*"Match allRun for every tool
Exact valueExact match"Bash" matches only the Bash tool
Values separated by &#124;Match several values"Write &#124; Edit" matches Write or Edit
Regular expressionPattern 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.
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.

Input

All events include the following common fields:
FieldDescription
session_idCurrent session ID
cwdCurrent working directory
hook_event_nameName of the event that fired
Different events add their own fields to this payload. Use jq to parse the input:
#!/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 valueScenario
startupStart a new session
resumeResume an existing session
compactContext compaction has completed
{
  "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.
{
  "reason": "prompt_input_exit"
}

UserPromptSubmit

Fires after the user submits a prompt and before the Agent starts processing it.
{
  "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.
{
  "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
{
  "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
{
  "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
{
  "agent_id": "a1b2c3d4",
  "agent_type": "task"
}

PreCompact

Fires before context compaction. Matcher: trigger type
Matcher valueScenario
manualThe user runs /compact manually
autoThe context window is full and compaction starts automatically
{
  "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.
{
  "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
{
  "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:
#!/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:
{
  "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:
#!/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:
#!/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.
Hooks - QwenWork