# Debugging tmux Session Isolation in Multi-Project SwarmForge Swarms: Complete Troubleshooting Guide

> Troubleshoot tmux session isolation issues in multi-project SwarmForge swarms. Learn how SwarmForge uses project-local tmux sockets to prevent interference and ensure smooth operation.

- Repository: [Robert C. Martin/swarm-forge](https://github.com/unclebob/swarm-forge)
- Tags: tutorial
- Published: 2026-09-01

---

**SwarmForge isolates AI agent swarms using project-local tmux sockets stored in `.swarmforge/tmux-socket`, preventing cross-project interference when multiple swarms run concurrently.**

Debugging **tmux session isolation in multi-project SwarmForge swarms** requires understanding how the orchestration layer creates per-project sockets, launches terminal adapters, and manages the handoff daemon. When isolation fails, agents from one project bleed into another's windows—often due to stale environment variables, incorrect socket paths, or daemon misconfiguration. This guide walks through the architecture, diagnostic steps, and fixes based on the SwarmForge source code implementation.

## How SwarmForge Isolates tmux Sessions

SwarmForge enforces **strict project boundaries** through a socket-based architecture. Each swarm operates within its own tmux server instance, with all runtime data confined to a local `.swarmforge/` directory.

### The Socket Creation Sequence

When you launch `./swarm` from a project directory, the system executes this initialization chain:

1. **Parse configuration** – [`swarmforge/swarmforge.conf`](https://github.com/unclebob/swarm-forge/blob/main/swarmforge/swarmforge.conf) is read for `window` and `window-invisible` role definitions
2. **Generate unique socket** – A socket file is created at `.swarmforge/tmux-socket` with absolute path resolution
3. **Bind all tmux operations** – Every subsequent command uses `-S "$TMUX_SOCKET"` for that project
4. **Spawn terminal surfaces** – The appropriate adapter from `swarmforge/scripts/terminal-adapters/` opens windows using the correct socket
5. **Start handoff daemon** – `handoffd.bb` monitors outbox directories and sends wake-up messages via the project-specific socket

According to the README (lines 374-377), this socket isolation is **fundamental to multi-project safety**. The `TMUX` environment variable is explicitly cleared before project launch to prevent accidental attachment to existing sessions.

### Key Components in Session Isolation

| Component | Location | Isolation Responsibility |
|-----------|----------|--------------------------|
| **Configuration parser** | [`swarmforge/swarmforge.conf`](https://github.com/unclebob/swarm-forge/blob/main/swarmforge/swarmforge.conf) | Defines which roles get visible vs. invisible windows |
| **Socket manager** | `.swarmforge/tmux-socket` (runtime) | Provides unique tmux server endpoint per project |
| **Terminal adapters** | `swarmforge/scripts/terminal-adapters/*.sh` | Bridge to host terminal using correct `-S` flag |
| **Handoff daemon** | `swarmforge/scripts/handoffd.bb` | Delivers notifications via project socket only |

## Common Session Isolation Failures

### Agents Appear in Wrong Project Windows

**Symptom:** Agents from Project A display in Project B's tmux windows, or commands intended for one swarm affect another.

**Root cause:** The `TMUX_SOCKET` environment variable points to a stale or incorrect path. This occurs when:
- A shell session retains `TMUX` or `SWARMFORGE_TMUX_SOCKET` from a previous project
- The `.swarmforge/tmux-socket` file was manually moved or copied between projects
- A terminal emulator restores environment variables from a previous session

**Diagnostic commands:**

```bash

# Verify socket isolation between two projects

cd /path/to/project-a
cat .swarmforge/tmux-socket

# Output: /path/to/project-a/.swarmforge/tmux-socket

cd /path/to/project-b
cat .swarmforge/tmux-socket

# Output: /path/to/project-b/.swarmforge/tmux-socket

# These paths must differ; if identical, isolation is compromised

```

**Fix:** Clear environment state before launching any swarm:

```bash
unset TMUX SWARMFORGE_TMUX_SOCKET
cd /path/to/your/project
./swarm

```

### New Windows Fail to Open

**Symptom:** A role configured with `window` in [`swarmforge.conf`](https://github.com/unclebob/swarm-forge/blob/main/swarmforge.conf) starts but no terminal surface appears.

**Root cause:** The terminal adapter cannot locate the socket or the backend detection failed. The [`swarm-terminal-adapter.sh`](https://github.com/unclebob/swarm-forge/blob/main/swarm-terminal-adapter.sh) dispatcher determines which adapter script to invoke, and each adapter must pass the socket path correctly.

**Diagnostic steps:**

```bash

# Run with explicit terminal selection and verbose output

SWARMFORGE_TERMINAL=ghostty ./swarm 2>&1 | tee swarm-launch.log

# Check adapter exit codes

echo $?  # Non-zero indicates adapter failure

# Verify the adapter references the socket correctly

grep -n "tmux -S" swarmforge/scripts/terminal-adapters/ghostty.sh

```

**Fix:** Confirm the adapter script includes proper socket handling. The [`ghostty.sh`](https://github.com/unclebob/swarm-forge/blob/main/ghostty.sh) reference implementation uses:

```bash

# From ghostty.sh - adapter contract

tmux -S "$SWARMFORGE_TMUX_SOCKET" new-window -t "$session_name" ...

```

### Handoff Notifications Stop Working

**Symptom:** Agents no longer respond to task assignments; the swarm appears frozen despite running processes.

**Root cause:** The `handoffd.bb` daemon attached to a stale socket, or the socket file was removed (e.g., by `tmux kill-server` or manual cleanup).

**Diagnostic verification:**

```bash

# Check daemon socket attachment in logs

grep "using tmux socket" /tmp/handoffd.log

# Verify socket file existence

ls -la .swarmforge/tmux-socket

# Test direct tmux communication

tmux -S "$(cat .swarmforge/tmux-socket)" list-sessions

```

**Fix:** Restart the daemon with correct socket detection:

```sh

# Terminate stale daemon

pkill -f "handoffd.bb.*$(basename $PWD)"

# Restart with verified socket path

export SWARMFORGE_TMUX_SOCKET=$(cat .swarmforge/tmux-socket)
./swarmforge/scripts/handoffd.bb >> /tmp/handoffd.log 2>&1 &

```

### tmux Copy-Mode Pauses Agent Execution

**Symptom:** An agent stops processing after you scroll or select text in its window.

**Root cause:** The tmux pane entered **copy-mode**, which blocks `send-keys` and other programmatic interaction until exited.

**Immediate fix:**

```bash

# Exit copy-mode in specific window

tmux -S "$(cat .swarmforge/tmux-socket)" \
     send-keys -t "$session:$window" q

# Or force interrupt

tmux -S "$(cat .swarmforge/tmux-socket)" \
     send-keys -t "$session:$window" C-c

```

## Preventing Cross-Project Bleed-Over

### Environment Hygiene Checklist

- **Never reuse `$TMUX`** — SwarmForge unsets this, but shell profiles or terminal emulators may restore it
- **Avoid global tmux configurations** — Remove `TMUX_TMPDIR` overrides that force shared socket directories
- **Use absolute socket paths** — The `.swarmforge/tmux-socket` file contains fully resolved paths, preventing `../` traversal issues

### Clean Teardown Procedures

The dashboard's **Teardown** button executes proper isolation cleanup:

```bash

# Equivalent manual cleanup

tmux -S "$(cat .swarmforge/tmux-socket)" kill-server
rm -f .swarmforge/tmux-socket
pkill -f "handoffd.bb.*$PROJECT_NAME"

```

Always use teardown rather than `kill -9` on tmux processes, which can leave orphaned socket files.

### Window Visibility Strategy

Configure roles appropriately in [`swarmforge/swarmforge.conf`](https://github.com/unclebob/swarm-forge/blob/main/swarmforge/swarmforge.conf):

- **`window`** — Opens visible terminal surface; useful for monitoring but susceptible to user interference (copy-mode, manual closing)
- **`window-invisible`** — Runs in background tmux window without terminal surface; eliminates accidental user disruption

For multi-project stability, prefer `window-invisible` for automated agents and reserve `window` only for roles requiring human supervision.

## Working with Terminal Adapters

SwarmForge's adapter architecture in `swarmforge/scripts/terminal-adapters/` provides terminal-agnostic socket handling.

### Socket Verification in Custom Adapters

When extending SwarmForge with new terminals, ensure your adapter follows this contract:

```bash

# Required: validate socket environment

if [[ -z "$SWARMFORGE_TMUX_SOCKET" ]]; then
    echo "Error: SWARMFORGE_TMUX_SOCKET not set" >&2
    exit 1
fi

if [[ ! -S "$SWARMFORGE_TMUX_SOCKET" ]]; then
    echo "Error: $SWARMFORGE_TMUX_SOCKET is not a valid socket" >&2
    exit 1
fi

# All tmux commands must use -S flag

tmux -S "$SWARMFORGE_TMUX_SOCKET" new-session -d -s "$session_name"

```

### Registering New Terminal Backends

To add WezTerm support (as referenced in the source analysis):

1. Create [`swarmforge/scripts/terminal-adapters/wezterm.sh`](https://github.com/unclebob/swarm-forge/blob/main/swarmforge/scripts/terminal-adapters/wezterm.sh) implementing the socket-forwarding contract
2. Update [`swarmforge/scripts/swarm-terminal-adapter.sh`](https://github.com/unclebob/swarm-forge/blob/main/swarmforge/scripts/swarm-terminal-adapter.sh) detection logic:

```sh

# Add to the case statement in swarm-terminal-adapter.sh

case "$SWARMFORGE_TERMINAL" in
  ghostty) terminal_backend="ghostty" ;;
  wezterm) terminal_backend="wezterm" ;;  # New entry

  *) terminal_backend="default" ;;
esac

```

3. Verify with: `SWARMFORGE_TERMINAL=wezterm ./swarm`

## Deep Dive: How handoffd.bb Maintains Isolation

The handoff daemon in `swarmforge/scripts/handoffd.bb` is **designed to never issue raw tmux commands directly to agents**. Per [`swarmforge/handoff-protocol.md`](https://github.com/unclebob/swarm-forge/blob/main/swarmforge/handoff-protocol.md), it:

- Watches `outbox/` directories for handoff files
- Sends **generic wake-up messages** via `tmux -S "$socket" send-keys`
- Never parses or forwards tmux commands from agent output

This design prevents malicious or buggy agents from injecting tmux commands into other projects. The socket path is read once at daemon startup and cached, making daemon restart necessary after any socket recreation.

## Summary

- **Socket isolation is the foundation** — Every SwarmForge project creates a unique tmux socket at `.swarmforge/tmux-socket`; all commands must use `-S` to reference it
- **Environment contamination causes bleed-over** — Always `unset TMUX` before switching projects; never reuse shell sessions with stale `SWARMFORGE_TMUX_SOCKET` values
- **Daemon restart resolves notification failures** — `handoffd.bb` caches its socket path; restart after any socket changes
- **Terminal adapters enforce the boundary** — Custom adapters must validate and consistently use `SWARMFORGE_TMUX_SOCKET`
- **Clean teardown prevents state leakage** — Use dashboard Teardown or manually remove socket files and kill the daemon

## Frequently Asked Questions

### How do I verify that two SwarmForge projects are properly isolated?

Check that their socket files differ and that no shared tmux server processes exist. Run `cat .swarmforge/tmux-socket` in each project directory—the outputs should be distinct absolute paths. Then run `lsof +D .swarmforge/tmux-socket` (or `fuser .swarmforge/tmux-socket`) to confirm separate tmux server processes are attached to each socket.

### Can I run multiple SwarmForge projects from the same terminal window?

Yes, provided you fully clear tmux-related environment variables between launches. Use `unset TMUX SWARMFORGE_TMUX_SOCKET` before each `./swarm` invocation. For shell convenience, wrap project switching in a function that performs this cleanup automatically.

### Why does my agent stop responding after I scroll its window?

tmux copy-mode captures keyboard focus and blocks `send-keys` delivery. Press **q** to exit copy-mode, or run `tmux -S "$(cat .swarmforge/tmux-socket)" send-keys -t <target> q` from another terminal. Consider using `window-invisible` for automated roles to prevent this user-interaction issue.

### What happens if I delete `.swarmforge/tmux-socket` while a swarm is running?

The tmux server continues running but becomes unreachable through standard SwarmForge commands. The handoff daemon will fail to send wake-up messages. Recover by identifying the orphaned tmux process (`ps aux | grep tmux`), killing it, and restarting the swarm with fresh socket creation via `./swarm`.