This commit is contained in:
ryan
2026-06-18 18:58:07 +08:00
parent 48421c12de
commit 22fbfc92c6
8 changed files with 1182 additions and 1 deletions
+445
View File
@@ -0,0 +1,445 @@
# 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
1. **Create a hook file**: `touch .dmux-hooks/worktree_created`
2. **Make it executable**: `chmod +x .dmux-hooks/worktree_created`
3. **Add shebang**: Start with `#!/bin/bash`
4. **Use environment variables**: Access `$DMUX_ROOT`, `$DMUX_WORKTREE_PATH`, etc.
5. **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_created` blocks 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
```bash
DMUX_ROOT="/path/to/project" # Project root directory
DMUX_SERVER_PORT="3142" # HTTP server port
```
### Pane Context (most hooks)
```bash
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
```bash
DMUX_WORKTREE_PATH="/path/.dmux/worktrees/fix-auth-bug"
DMUX_BRANCH="fix-auth-bug" # Same as slug
```
### Bootstrap Progress Context
```bash
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.
```bash
status() {
if [ "${DMUX_PROGRESS:-0}" = "1" ]; then
echo "${DMUX_STATUS_PREFIX:-DMUX_STATUS:} $*"
else
echo "[Hook] $*"
fi
}
```
### Merge Context
```bash
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
```bash
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
```bash
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
```bash
#!/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
```bash
#!/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
```bash
#!/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
```bash
#!/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
```bash
#!/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
1. **Always start with shebang**: `#!/bin/bash`
2. **Set error handling**: `set -e` (exit on error)
3. **Make executable**: `chmod +x .dmux-hooks/hook_name`
4. **Background long operations**: Append `&` to avoid blocking
5. **Check for required tools**: `command -v tool &> /dev/null`
6. **Log for debugging**: `echo "[Hook] message" >> "$DMUX_ROOT/.dmux/hooks.log"`
7. **Handle missing vars gracefully**: `[ -z "$VAR" ] && exit 0`
8. **Use silent curl**: `curl -s` to avoid noise in logs
9. **Clean up temp files**: Remove files in `/tmp/`
10. **Test before committing**: Run hooks manually with mock env vars
## Testing Hooks
### Manual Testing
```bash
# 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
```bash
# Check for syntax errors without running
bash -n ./.dmux-hooks/worktree_created
```
### Shellcheck (if available)
```bash
shellcheck ./.dmux-hooks/worktree_created
```
## Project Context Analysis
Before creating hooks, analyze these files in the project:
### Package Manager Detection
```bash
# 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
```bash
# Read package.json to find test command
cat package.json | grep '"test"'
# Or with jq:
jq -r '.scripts.test' package.json
```
### Dev Command Discovery
```bash
# Read package.json to find dev command
cat package.json | grep '"dev"'
# Or with jq:
jq -r '.scripts.dev' package.json
```
### Environment Variables
```bash
# Check for .env files to copy
ls -la | grep '.env'
```
### Build System
```bash
# 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:
1. **Check if file exists**: `ls -la .dmux-hooks/`
2. **Check permissions**: Should show `x` in `rwxr-xr-x`
3. **Check syntax**: `bash -n .dmux-hooks/hook_name`
4. **Test manually**: Set env vars and run
5. **Check logs**: dmux logs to stderr with `[Hooks]` prefix
6. **Simplify**: Remove complex parts, test basic version
7. **Check tool availability**: `command -v required_tool`
### Debug Mode
```bash
#!/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 -e` for 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.md` in project root
- **Claude-specific tips**: See `CLAUDE.md` in `.dmux-hooks/`
- **Examples**: Check `.dmux-hooks/examples/` directory
- **dmux API**: See `API.md` for REST endpoints
---
*This documentation was auto-generated from dmux source code.*
*Version: 2026-05-25*
+445
View File
@@ -0,0 +1,445 @@
# 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
1. **Create a hook file**: `touch .dmux-hooks/worktree_created`
2. **Make it executable**: `chmod +x .dmux-hooks/worktree_created`
3. **Add shebang**: Start with `#!/bin/bash`
4. **Use environment variables**: Access `$DMUX_ROOT`, `$DMUX_WORKTREE_PATH`, etc.
5. **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_created` blocks 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
```bash
DMUX_ROOT="/path/to/project" # Project root directory
DMUX_SERVER_PORT="3142" # HTTP server port
```
### Pane Context (most hooks)
```bash
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
```bash
DMUX_WORKTREE_PATH="/path/.dmux/worktrees/fix-auth-bug"
DMUX_BRANCH="fix-auth-bug" # Same as slug
```
### Bootstrap Progress Context
```bash
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.
```bash
status() {
if [ "${DMUX_PROGRESS:-0}" = "1" ]; then
echo "${DMUX_STATUS_PREFIX:-DMUX_STATUS:} $*"
else
echo "[Hook] $*"
fi
}
```
### Merge Context
```bash
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
```bash
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
```bash
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
```bash
#!/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
```bash
#!/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
```bash
#!/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
```bash
#!/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
```bash
#!/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
1. **Always start with shebang**: `#!/bin/bash`
2. **Set error handling**: `set -e` (exit on error)
3. **Make executable**: `chmod +x .dmux-hooks/hook_name`
4. **Background long operations**: Append `&` to avoid blocking
5. **Check for required tools**: `command -v tool &> /dev/null`
6. **Log for debugging**: `echo "[Hook] message" >> "$DMUX_ROOT/.dmux/hooks.log"`
7. **Handle missing vars gracefully**: `[ -z "$VAR" ] && exit 0`
8. **Use silent curl**: `curl -s` to avoid noise in logs
9. **Clean up temp files**: Remove files in `/tmp/`
10. **Test before committing**: Run hooks manually with mock env vars
## Testing Hooks
### Manual Testing
```bash
# 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
```bash
# Check for syntax errors without running
bash -n ./.dmux-hooks/worktree_created
```
### Shellcheck (if available)
```bash
shellcheck ./.dmux-hooks/worktree_created
```
## Project Context Analysis
Before creating hooks, analyze these files in the project:
### Package Manager Detection
```bash
# 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
```bash
# Read package.json to find test command
cat package.json | grep '"test"'
# Or with jq:
jq -r '.scripts.test' package.json
```
### Dev Command Discovery
```bash
# Read package.json to find dev command
cat package.json | grep '"dev"'
# Or with jq:
jq -r '.scripts.dev' package.json
```
### Environment Variables
```bash
# Check for .env files to copy
ls -la | grep '.env'
```
### Build System
```bash
# 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:
1. **Check if file exists**: `ls -la .dmux-hooks/`
2. **Check permissions**: Should show `x` in `rwxr-xr-x`
3. **Check syntax**: `bash -n .dmux-hooks/hook_name`
4. **Test manually**: Set env vars and run
5. **Check logs**: dmux logs to stderr with `[Hooks]` prefix
6. **Simplify**: Remove complex parts, test basic version
7. **Check tool availability**: `command -v required_tool`
### Debug Mode
```bash
#!/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 -e` for 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.md` in project root
- **Claude-specific tips**: See `CLAUDE.md` in `.dmux-hooks/`
- **Examples**: Check `.dmux-hooks/examples/` directory
- **dmux API**: See `API.md` for REST endpoints
---
*This documentation was auto-generated from dmux source code.*
*Version: 2026-05-25*
+53
View File
@@ -0,0 +1,53 @@
# dmux Hooks
This directory contains hooks that run automatically at key lifecycle events in dmux.
## Quick Start
1. **Read the documentation**:
- `AGENTS.md` - Complete reference (for any AI agent)
- `CLAUDE.md` - Same content (Claude Code looks for this filename)
2. **Check examples**:
- `examples/` directory contains starter templates
3. **Create a hook**:
```bash
touch worktree_created
chmod +x worktree_created
nano worktree_created
```
4. **Test it**:
```bash
export DMUX_ROOT="$(pwd)"
export DMUX_WORKTREE_PATH="$(pwd)"
./worktree_created
```
## Available Hooks
- `before_pane_create` - Before pane creation
- `pane_created` - After pane created
- `worktree_created` - After worktree setup
- `before_pane_close` - Before closing
- `pane_closed` - After closed
- `before_worktree_remove` - Before worktree removal
- `worktree_removed` - After worktree removed
- `pre_merge` - Before merge
- `post_merge` - After merge
- `run_test` - When running tests
- `run_dev` - When starting dev server
## Documentation
See `AGENTS.md` or `CLAUDE.md` for complete documentation including:
- Environment variables
- HTTP callback API
- Common patterns
- Best practices
- Testing strategies
## Note
This directory is **version controlled**. Hooks you create here will be shared with your team.
+66
View File
@@ -0,0 +1,66 @@
#!/bin/bash
# Example: post_merge hook
#
# This hook runs after a successful merge into the target branch.
# Use it to trigger deployments, close issues, notify teams, etc.
set -e
echo "[Hook] Post-merge processing for $DMUX_SLUG → $DMUX_TARGET_BRANCH"
cd "$DMUX_ROOT"
# Push to remote if merging to main/master
if [ "$DMUX_TARGET_BRANCH" = "main" ] || [ "$DMUX_TARGET_BRANCH" = "master" ]; then
echo "[Hook] Pushing to origin/$DMUX_TARGET_BRANCH"
git push origin "$DMUX_TARGET_BRANCH"
# Optional: Trigger deployment
# if [ -n "$VERCEL_TOKEN" ]; then
# echo "[Hook] Triggering Vercel deployment..."
# curl -X POST "https://api.vercel.com/v1/deployments" \
# -H "Authorization: Bearer $VERCEL_TOKEN" \
# -H "Content-Type: application/json" \
# -d '{
# "name": "my-project",
# "gitSource": {
# "type": "github",
# "ref": "main"
# }
# }'
# fi
fi
# Close related GitHub issue (if prompt contains #123 format)
ISSUE_NUM=$(echo "$DMUX_PROMPT" | grep -oP '#\K\d+' | head -1)
if [ -n "$ISSUE_NUM" ]; then
echo "[Hook] Closing GitHub issue #$ISSUE_NUM"
if command -v gh &> /dev/null; then
gh issue close "$ISSUE_NUM" \
-c "Resolved in branch $DMUX_SLUG, merged to $DMUX_TARGET_BRANCH" \
2>/dev/null || echo "[Hook] Warning: Failed to close issue (maybe already closed?)"
else
echo "[Hook] GitHub CLI (gh) not found, skipping issue close"
fi
fi
# Send notification to Slack
# if [ -n "$SLACK_WEBHOOK" ]; then
# echo "[Hook] Sending Slack notification"
# curl -s -X POST "$SLACK_WEBHOOK" \
# -H "Content-Type: application/json" \
# -d "{
# \"text\": \"Merged: $DMUX_SLUG → $DMUX_TARGET_BRANCH\",
# \"blocks\": [
# {
# \"type\": \"section\",
# \"text\": {
# \"type\": \"mrkdwn\",
# \"text\": \"*Branch Merged* :rocket:\n\n*From:* \`$DMUX_SLUG\`\n*To:* \`$DMUX_TARGET_BRANCH\`\n*Task:* $DMUX_PROMPT\"
# }
# }
# ]
# }" > /dev/null
# fi
echo "[Hook] Post-merge processing complete"
+62
View File
@@ -0,0 +1,62 @@
#!/bin/bash
# Example: run_dev hook
#
# This hook starts a dev server and optionally creates a tunnel for sharing.
# It reports the server URL back to dmux via the HTTP API.
set -e
echo "[Hook] Starting dev server for $DMUX_SLUG"
cd "$DMUX_WORKTREE_PATH"
API_URL="http://localhost:$DMUX_SERVER_PORT/api/panes/$DMUX_PANE_ID/dev"
# Update status: starting
curl -s -X PUT "$API_URL" \
-H "Content-Type: application/json" \
-d '{"status": "running"}' > /dev/null
# Start dev server in background
# Adjust the command for your project (pnpm dev, npm run dev, vite, etc.)
LOG_FILE="/tmp/dmux-dev-$DMUX_PANE_ID.log"
pnpm dev > "$LOG_FILE" 2>&1 &
DEV_PID=$!
# Wait for server to be ready
echo "[Hook] Waiting for dev server to start..."
sleep 5
# Detect port from log output
# Adjust the grep pattern for your dev server's output format
PORT=$(grep -oP '(?<=localhost:)\d+' "$LOG_FILE" | head -1)
if [ -z "$PORT" ]; then
echo "[Hook] Warning: Could not detect port from logs, using default 3000"
PORT=3000
fi
LOCAL_URL="http://localhost:$PORT"
echo "[Hook] Dev server running at $LOCAL_URL"
# Optional: Create a public tunnel (uncomment to enable)
# Requires ngrok, cloudflared, or another tunneling tool
# Example with cloudflared:
# TUNNEL_URL=$(cloudflared tunnel --url "$LOCAL_URL" 2>&1 | \
# grep -oP 'https://[a-z0-9-]+\.trycloudflare\.com' | head -1)
# Example with ngrok:
# TUNNEL_URL=$(ngrok http $PORT --log=stdout 2>&1 | \
# grep -oP 'url=https://[^"]+' | head -1 | cut -d= -f2)
# For now, just use local URL (uncomment tunnel code above to enable)
FINAL_URL="$LOCAL_URL"
# Report status back to dmux
curl -s -X PUT "$API_URL" \
-H "Content-Type: application/json" \
-d "{\"status\": \"running\", \"url\": \"$FINAL_URL\"}" > /dev/null
echo "[Hook] Dev server ready at: $FINAL_URL"
echo "[Hook] Dev server PID: $DEV_PID"
echo "[Hook] Log file: $LOG_FILE"
+61
View File
@@ -0,0 +1,61 @@
#!/bin/bash
# Example: run_test hook
#
# This hook runs tests and reports the status back to dmux via the HTTP API.
# Status updates appear in real-time in the dmux UI.
set -e
echo "[Hook] Running tests for $DMUX_SLUG"
cd "$DMUX_WORKTREE_PATH"
API_URL="http://localhost:$DMUX_SERVER_PORT/api/panes/$DMUX_PANE_ID/test"
# Update status: running
curl -s -X PUT "$API_URL" \
-H "Content-Type: application/json" \
-d '{"status": "running"}' > /dev/null
echo "[Hook] Running test suite..."
# Capture test output
OUTPUT_FILE="/tmp/dmux-test-$DMUX_PANE_ID.txt"
# Run tests (adjust command for your project)
# Examples:
# - pnpm test
# - npm test
# - vitest run
# - jest
# - pytest
# - cargo test
if pnpm test > "$OUTPUT_FILE" 2>&1; then
STATUS="passed"
echo "[Hook] Tests passed ✓"
else
STATUS="failed"
echo "[Hook] Tests failed ✗"
fi
# Get output (truncate if too long)
OUTPUT=$(head -c 5000 "$OUTPUT_FILE")
# Report results back to dmux
curl -s -X PUT "$API_URL" \
-H "Content-Type: application/json" \
-d "$(jq -n \
--arg status "$STATUS" \
--arg output "$OUTPUT" \
'{status: $status, output: $output}')" > /dev/null
# Cleanup
rm -f "$OUTPUT_FILE"
echo "[Hook] Test results reported to dmux"
# Exit with test status
if [ "$STATUS" = "passed" ]; then
exit 0
else
exit 1
fi
+48
View File
@@ -0,0 +1,48 @@
#!/bin/bash
# Example: worktree_created hook
#
# This hook runs after a new worktree is created and before the agent launches.
# Use it to set up the worktree environment (install deps, copy configs, etc.)
# Stdout/stderr is streamed into the new pane's setup UI while this hook runs.
# dmux waits for this hook without a fixed timeout.
set -e # Exit on error
status() {
if [ "${DMUX_PROGRESS:-0}" = "1" ]; then
echo "${DMUX_STATUS_PREFIX:-DMUX_STATUS:} $*"
else
echo "[Hook] $*"
fi
}
status "Setting up worktree: $DMUX_SLUG"
cd "$DMUX_WORKTREE_PATH"
# Install dependencies before the agent launches.
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
fi
# Copy environment file if it exists
if [ -f "$DMUX_ROOT/.env.local" ]; then
status "Copying .env.local"
cp "$DMUX_ROOT/.env.local" "$DMUX_WORKTREE_PATH/.env.local"
fi
# Keep existing git author identity.
# Do not set git user.name/user.email in this hook.
# Create a log entry
echo "[$(date)] Created worktree: $DMUX_SLUG | Agent: $DMUX_AGENT | Prompt: $DMUX_PROMPT" \
>> "$DMUX_ROOT/.dmux/worktree_history.log"
status "Worktree setup complete"
+2 -1
View File
@@ -52,4 +52,5 @@ go.work.sum
*-source.*
.codex*
/bin/
/bin/
.dmux/