Skip to main content
Tool hooks are experimental. Expect breaking changes while we iterate.
The easiest way to set up tool hooks is to ask Mux. Just tell Mux what you want (e.g., “lint Python files after edits” or “block force pushes”) and it will create the hook scripts for you.
Tool hooks let you run your own scripts before and after Mux tool executions.

What do you want to do?


Block dangerous commands

Create .mux/tool_pre to validate commands before they run. Exit non-zero to block:
#!/usr/bin/env bash
# .mux/tool_pre - runs before every tool

if [[ "$MUX_TOOL" == "bash" ]]; then
  script="$MUX_TOOL_INPUT_SCRIPT"

  if echo "$script" | grep -q 'push.*--force'; then
    echo "❌ Force push blocked" >&2
    exit 1
  fi

  if echo "$script" | grep -q 'rm -rf /'; then
    echo "❌ Dangerous rm blocked" >&2
    exit 1
  fi
fi

exit 0  # Allow tool to run
chmod +x .mux/tool_pre
The agent sees your error message and can adjust its approach.

Lint after file edits

Create .mux/tool_post to run validation after tools complete:
#!/usr/bin/env bash
# .mux/tool_post - runs after every tool
set -euo pipefail

[[ "$MUX_TOOL" == file_edit_* ]] || exit 0
file="${MUX_TOOL_INPUT_FILE_PATH:-}"
[[ -n "$file" ]] || exit 0

case "$file" in
  *.py)
    ruff check "$file"
    ;;
  *.ts|*.tsx)
    npx tsc --noEmit "$file"
    ;;
esac
chmod +x .mux/tool_post
Lint errors appear in hook_output and the agent can fix them.
hook_output is only shown in the UI when the hook produces output. For a cleaner experience, only print output when the hook has an effect—e.g., skip “Formatted: file” messages if the file was already formatted.

Set up environment

Create .mux/tool_env to configure your shell environment. This file is sourced before every bash tool call:
# .mux/tool_env - sourced before bash commands

# direnv
eval "$(direnv export bash 2>/dev/null)" || true

# nvm
# export NVM_DIR="$HOME/.nvm"
# [ -s "$NVM_DIR/nvm.sh" ] && source "$NVM_DIR/nvm.sh"

# Python virtualenv
# source .venv/bin/activate 2>/dev/null || true
Unlike hooks, tool_env doesn’t need to be executable—it’s sourced, not run. It only affects bash tools.

Reference

All hooks receive these environment variables:
VariableDescription
MUX_TOOLTool name: bash, file_edit_replace_string, file_read, etc.
MUX_WORKSPACE_IDCurrent workspace identifier
MUX_TOOL_INPUT_PATHPath to file containing full tool input (always set)
Mux flattens the tool input into MUX_TOOL_INPUT_<...> environment variables (see the appendix below). Fields longer than 8KB are omitted—use MUX_TOOL_INPUT_PATH for full access.Post-hook only (tool_post):
VariableDescription
MUX_TOOL_RESULT_PATHPath to file containing full tool result
Flattened result fields are available as MUX_TOOL_RESULT_<...>. Fields longer than 8KB are omitted—use MUX_TOOL_RESULT_PATH for full access.
Exit Codetool_pre behaviortool_post behavior
0Tool executes normallySuccess, output shown to agent
Non-zeroTool blocked, error shown to agentFailure, error shown in hook_output
Mux searches for each hook file in this order:
  1. Project-level: .mux/<hook>
  2. User-level: ~/.mux/<hook>
This applies to tool_pre, tool_post, and tool_env.For SSH workspaces, hooks execute on the remote machine.
Hooks must complete within 10 seconds or they’re terminated. Long-running tools (builds, tests) don’t count against this—only hook execution time.Keep hooks fast—if you need longer operations, consider running them asynchronously or in the background.
Feature.mux/tool_pre.mux/tool_post.mux/tool_env
PurposeBlock dangerous commandsLint/validate resultsEnvironment setup
RunsBefore toolAfter toolSourced in bash shell
Applies toAll toolsAll toolsbash tool only
Use caseBlock force-pushRun ruff/eslintdirenv, nvm, virtualenv
Mux also provides flattened tool input env vars so hook scripts can stay compact.
  • Scalars become MUX_TOOL_INPUT_<FIELD>
  • Nested objects become MUX_TOOL_INPUT_<PARENT>_<CHILD>
  • Arrays also include ..._COUNT and per-index variables like ..._<INDEX>
If a value is too large for the environment, it may be omitted (not set). Mux also caps the number of flattened env vars and array elements to keep hook execution reliable.

More examples

#!/usr/bin/env bash
# .mux/tool_post

if [[ "$MUX_TOOL" == file_edit_* ]]; then
  file="$MUX_TOOL_INPUT_FILE_PATH"
  prettier --write --log-level silent "$file" 2>/dev/null || true
fi
#!/usr/bin/env bash
# .mux/tool_post

# Tool results are available via MUX_TOOL_RESULT_* if needed.
echo "$(date '+%H:%M:%S') $MUX_TOOL completed" >> /tmp/mux-tools.log
#!/usr/bin/env python3
# .mux/tool_pre

import os, sys

tool = os.environ.get('MUX_TOOL', '')
script = os.environ.get('MUX_TOOL_INPUT_SCRIPT', '')

if tool == 'bash':
    if 'rm -rf /' in script:
        print("❌ Blocked dangerous command", file=sys.stderr)
        sys.exit(1)

sys.exit(0)  # Allow