Files
OpenFlare/.dmux-hooks/AGENTS.md
T
2026-06-18 18:58:07 +08:00

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

  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

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

  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

# 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:

  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

#!/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