13 KiB
dmux Hooks System - Agent Reference
Auto-generated documentation for AI agents
This document contains everything an AI agent needs to create, modify, and understand dmux hooks. It is automatically generated from the dmux source code and embedded in the binary.
What You're Working On
You are editing hooks for dmux, a tmux pane manager that creates AI-powered development workflows. Each pane runs in its own git worktree with an AI agent.
Your Goal
Create executable bash scripts in .dmux-hooks/ that run automatically at key lifecycle events.
Quick Start
- Create a hook file:
touch .dmux-hooks/worktree_created - Make it executable:
chmod +x .dmux-hooks/worktree_created - Add shebang: Start with
#!/bin/bash - Use environment variables: Access
$DMUX_ROOT,$DMUX_WORKTREE_PATH, etc. - Test it: Set env vars manually and run the script
Hook Execution Model
- Mostly non-blocking: Most hooks run in background (detached processes)
- Bootstrap-gated:
worktree_createdblocks agent launch so setup can finish first, with no fixed timeout - Live bootstrap output: During
worktree_created, stdout/stderr is streamed into the new pane's setup UI - Failure behavior: Background hook errors are logged; gated hooks can abort the operation
- Environment-based: All context passed via environment variables
- Version controlled: Hooks in
.dmux-hooks/are shared with team - Priority resolution:
.dmux-hooks/→.dmux/hooks/→~/.dmux/hooks/
Available Hooks
Pane Lifecycle Hooks
| Hook | When | Common Use Cases |
|---|---|---|
before_pane_create |
Before pane creation | Validation, notifications, pre-flight checks |
pane_created |
After pane, before worktree | Configure tmux settings, prepare environment |
worktree_created |
After worktree creation, before agent launch | Install deps, copy configs, setup git |
before_pane_close |
Before closing | Save state, backup uncommitted work |
pane_closed |
After closed | Cleanup resources, analytics, notifications |
Worktree Lifecycle Hooks
| Hook | When | Common Use Cases |
|---|---|---|
before_worktree_remove |
Before worktree removal | Archive worktree, save artifacts |
worktree_removed |
After worktree removed | Cleanup external references |
Merge Lifecycle Hooks
| Hook | When | Common Use Cases |
|---|---|---|
pre_merge |
Before merge operation | Run final tests, create backups |
post_merge |
After successful merge | Deploy, close issues, notify team |
Interactive Hooks (with HTTP callbacks)
| Hook | When | Common Use Cases |
|---|---|---|
run_test |
When tests triggered | Run test suite, report status via HTTP |
run_dev |
When dev server triggered | Start dev server, create tunnel, report URL |
Environment Variables
Always Available
DMUX_ROOT="/path/to/project" # Project root directory
DMUX_SERVER_PORT="3142" # HTTP server port
Pane Context (most hooks)
DMUX_PANE_ID="dmux-1234567890" # dmux pane identifier
DMUX_SLUG="fix-auth-bug" # Branch/worktree name
DMUX_PROMPT="Fix authentication bug" # User's prompt
DMUX_AGENT="claude" # Agent type (registry id, e.g. claude, codex, opencode)
DMUX_TMUX_PANE_ID="%38" # tmux pane ID
Worktree Context
DMUX_WORKTREE_PATH="/path/.dmux/worktrees/fix-auth-bug"
DMUX_BRANCH="fix-auth-bug" # Same as slug
Bootstrap Progress Context
DMUX_PROGRESS="1" # Set when output is shown in the new pane setup UI
DMUX_STATUS_PREFIX="DMUX_STATUS:" # Prefix for clean status messages
worktree_created can emit progress while it runs. Any stdout/stderr line is shown in the setup UI; lines prefixed with $DMUX_STATUS_PREFIX are displayed without the prefix.
status() {
if [ "${DMUX_PROGRESS:-0}" = "1" ]; then
echo "${DMUX_STATUS_PREFIX:-DMUX_STATUS:} $*"
else
echo "[Hook] $*"
fi
}
Merge Context
DMUX_TARGET_BRANCH="main" # Branch being merged into
HTTP Callback API
Interactive hooks (run_test and run_dev) can update dmux UI via HTTP.
Update Test Status
curl -X PUT "http://localhost:$DMUX_SERVER_PORT/api/panes/$DMUX_PANE_ID/test" -H "Content-Type: application/json" -d '{"status": "running", "output": "optional test output"}'
# Status values: "running" | "passed" | "failed"
Update Dev Server
curl -X PUT "http://localhost:$DMUX_SERVER_PORT/api/panes/$DMUX_PANE_ID/dev" -H "Content-Type: application/json" -d '{"status": "running", "url": "http://localhost:3000"}'
# Status values: "running" | "stopped"
# url: Can be localhost or tunnel URL (ngrok, cloudflared, etc.)
Common Patterns
Pattern 1: Install Dependencies
#!/bin/bash
# .dmux-hooks/worktree_created
cd "$DMUX_WORKTREE_PATH"
status() {
if [ "${DMUX_PROGRESS:-0}" = "1" ]; then
echo "${DMUX_STATUS_PREFIX:-DMUX_STATUS:} $*"
else
echo "[Hook] $*"
fi
}
if [ -f "pnpm-lock.yaml" ]; then
status "Installing dependencies with pnpm"
pnpm install --prefer-offline
elif [ -f "package-lock.json" ]; then
status "Installing dependencies with npm"
npm install
elif [ -f "yarn.lock" ]; then
status "Installing dependencies with yarn"
yarn install
elif [ -f "Gemfile" ]; then
status "Installing gems"
bundle install
elif [ -f "requirements.txt" ]; then
status "Installing Python dependencies"
pip install -r requirements.txt
elif [ -f "Cargo.toml" ]; then
status "Building Rust project"
cargo build
fi
Pattern 2: Copy Configuration
#!/bin/bash
# .dmux-hooks/worktree_created
# Copy environment file
if [ -f "$DMUX_ROOT/.env.local" ]; then
cp "$DMUX_ROOT/.env.local" "$DMUX_WORKTREE_PATH/.env.local"
fi
# Copy other config files
for file in .env.development .npmrc .yarnrc; do
if [ -f "$DMUX_ROOT/$file" ]; then
cp "$DMUX_ROOT/$file" "$DMUX_WORKTREE_PATH/$file"
fi
done
Pattern 3: Run Tests with Status Updates
#!/bin/bash
# .dmux-hooks/run_test
set -e
cd "$DMUX_WORKTREE_PATH"
API="http://localhost:$DMUX_SERVER_PORT/api/panes/$DMUX_PANE_ID/test"
# Update: starting
curl -s -X PUT "$API" -H "Content-Type: application/json" -d '{"status": "running"}' > /dev/null
# Run tests and capture output
OUTPUT_FILE="/tmp/dmux-test-$DMUX_PANE_ID.txt"
if pnpm test > "$OUTPUT_FILE" 2>&1; then
STATUS="passed"
else
STATUS="failed"
fi
# Get output (truncate if too long)
OUTPUT=$(head -c 5000 "$OUTPUT_FILE")
# Update: complete
curl -s -X PUT "$API" -H "Content-Type: application/json" -d "$(jq -n --arg status "$STATUS" --arg output "$OUTPUT" '{status: $status, output: $output}')" > /dev/null
rm -f "$OUTPUT_FILE"
Pattern 4: Dev Server with Tunnel
#!/bin/bash
# .dmux-hooks/run_dev
set -e
cd "$DMUX_WORKTREE_PATH"
API="http://localhost:$DMUX_SERVER_PORT/api/panes/$DMUX_PANE_ID/dev"
# Start dev server in background
LOG_FILE="/tmp/dmux-dev-$DMUX_PANE_ID.log"
pnpm dev > "$LOG_FILE" 2>&1 &
DEV_PID=$!
# Wait for server to start
sleep 5
# Detect port from logs
PORT=$(grep -oP 'localhost:Kd+' "$LOG_FILE" | head -1)
[ -z "$PORT" ] && PORT=3000
# Optional: Create tunnel with cloudflared
if command -v cloudflared &> /dev/null; then
TUNNEL=$(cloudflared tunnel --url "http://localhost:$PORT" 2>&1 | grep -oP 'https://[a-z0-9-]+.trycloudflare.com' | head -1)
URL="${TUNNEL:-http://localhost:$PORT}"
else
URL="http://localhost:$PORT"
fi
# Report status
curl -s -X PUT "$API" -H "Content-Type: application/json" -d "{"status": "running", "url": "$URL"}" > /dev/null
echo "[Hook] Dev server running at $URL (PID: $DEV_PID)"
Pattern 5: Post-Merge Deployment
#!/bin/bash
# .dmux-hooks/post_merge
set -e
cd "$DMUX_ROOT"
# Only deploy from main/master
if [ "$DMUX_TARGET_BRANCH" != "main" ] && [ "$DMUX_TARGET_BRANCH" != "master" ]; then
exit 0
fi
# Push to remote
git push origin "$DMUX_TARGET_BRANCH"
# Trigger deployment (example: Vercel)
if [ -n "$VERCEL_TOKEN" ]; then
curl -s -X POST "https://api.vercel.com/v1/deployments" -H "Authorization: Bearer $VERCEL_TOKEN" -H "Content-Type: application/json" -d '{"name": "my-project"}' > /dev/null
fi
# Close GitHub issue if prompt contains #123
ISSUE=$(echo "$DMUX_PROMPT" | grep -oP '#Kd+' | head -1)
if [ -n "$ISSUE" ] && command -v gh &> /dev/null; then
gh issue close "$ISSUE" -c "Resolved in $DMUX_SLUG, merged to $DMUX_TARGET_BRANCH" 2>/dev/null || true
fi
Best Practices
- Always start with shebang:
#!/bin/bash - Set error handling:
set -e(exit on error) - Make executable:
chmod +x .dmux-hooks/hook_name - Background long operations: Append
&to avoid blocking - Check for required tools:
command -v tool &> /dev/null - Log for debugging:
echo "[Hook] message" >> "$DMUX_ROOT/.dmux/hooks.log" - Handle missing vars gracefully:
[ -z "$VAR" ] && exit 0 - Use silent curl:
curl -sto avoid noise in logs - Clean up temp files: Remove files in
/tmp/ - Test before committing: Run hooks manually with mock env vars
Testing Hooks
Manual Testing
# 1. Set environment variables
export DMUX_ROOT="$(pwd)"
export DMUX_PANE_ID="test-pane"
export DMUX_SLUG="test-branch"
export DMUX_WORKTREE_PATH="$(pwd)"
export DMUX_SERVER_PORT="3142"
export DMUX_AGENT="claude"
export DMUX_PROMPT="Test prompt"
# 2. Run hook directly
./.dmux-hooks/worktree_created
# 3. Check exit code
echo $? # Should be 0 for success
Syntax Check
# Check for syntax errors without running
bash -n ./.dmux-hooks/worktree_created
Shellcheck (if available)
shellcheck ./.dmux-hooks/worktree_created
Project Context Analysis
Before creating hooks, analyze these files in the project:
Package Manager Detection
# Check which package manager is used
if [ -f "pnpm-lock.yaml" ]; then
# Use: pnpm install, pnpm test, pnpm dev
elif [ -f "package-lock.json" ]; then
# Use: npm install, npm test, npm run dev
elif [ -f "yarn.lock" ]; then
# Use: yarn install, yarn test, yarn dev
fi
Test Command Discovery
# Read package.json to find test command
cat package.json | grep '"test"'
# Or with jq:
jq -r '.scripts.test' package.json
Dev Command Discovery
# Read package.json to find dev command
cat package.json | grep '"dev"'
# Or with jq:
jq -r '.scripts.dev' package.json
Environment Variables
# Check for .env files to copy
ls -la | grep '.env'
Build System
# Detect build system
if [ -f "vite.config.ts" ]; then
# Vite project
elif [ -f "next.config.js" ]; then
# Next.js project
elif [ -f "nuxt.config.ts" ]; then
# Nuxt project
fi
Common Mistakes to Avoid
❌ Blocking operations: sleep 60 (blocks dmux)
✅ Background long tasks: slow_operation &
❌ Hardcoded paths: /Users/me/project
✅ Use variables: "$DMUX_ROOT"
❌ Assuming tools exist: pnpm install
✅ Check first: command -v pnpm && pnpm install
❌ No error handling: Script fails silently
✅ Set error mode: set -e or check exit codes
❌ Forgetting executable bit: Hook won't run
✅ Make executable: chmod +x
❌ Noisy output: Clutters dmux logs
✅ Silent operations: curl -s, > /dev/null 2>&1
❌ Not testing: Deploy and hope ✅ Test manually: Run with mock env vars first
Debugging
If a hook isn't working:
- Check if file exists:
ls -la .dmux-hooks/ - Check permissions: Should show
xinrwxr-xr-x - Check syntax:
bash -n .dmux-hooks/hook_name - Test manually: Set env vars and run
- Check logs: dmux logs to stderr with
[Hooks]prefix - Simplify: Remove complex parts, test basic version
- Check tool availability:
command -v required_tool
Debug Mode
#!/bin/bash
# Add to top of hook for debugging
set -x # Print each command before executing
set -e # Exit on error
# Your hook logic here
Summary Checklist
When creating a new hook:
- Create file in
.dmux-hooks/ - Add shebang:
#!/bin/bash - Make executable:
chmod +x - Add
set -efor error handling - Use environment variables (never hardcode paths)
- Keep blocking hooks chatty with status output
- Background long operations with
&only when the hook does not gate the operation - Check for required tools before using
- Test manually with mock env vars
- Add comments explaining what it does
- Commit to version control
Getting Help
- Full documentation: See
HOOKS.mdin project root - Claude-specific tips: See
CLAUDE.mdin.dmux-hooks/ - Examples: Check
.dmux-hooks/examples/directory - dmux API: See
API.mdfor REST endpoints
This documentation was auto-generated from dmux source code. Version: 2026-05-25