# How to Use the `swarm_handoff.sh` Helper Script in Swarm-Forge

> Learn to use the swarm_handoff.sh script from unclebob/swarm-forge. Validate draft handoff files and convert them into machine-ready payloads for Swarm-Forge roles.

- Repository: [Robert C. Martin/swarm-forge](https://github.com/unclebob/swarm-forge)
- Tags: how-to-guide
- Published: 2026-08-29

---

**The [`swarm_handoff.sh`](https://github.com/unclebob/swarm-forge/blob/main/swarm_handoff.sh) helper script validates draft handoff files and converts them into machine-ready payloads for moving work between Swarm-Forge roles.**

The [`swarm_handoff.sh`](https://github.com/unclebob/swarm-forge/blob/main/swarm_handoff.sh) script is the primary entry point for creating **handoffs** in the Swarm-Forge workflow system. According to the unclebob/swarm-forge source code, this thin Zsh wrapper delegates all processing to a Babashka script that enforces protocol compliance, validates recipients, and manages audit lifecycle.

## What [`swarm_handoff.sh`](https://github.com/unclebob/swarm-forge/blob/main/swarm_handoff.sh) Actually Does

At [`swarmforge/scripts/swarm_handoff.sh`](https://github.com/unclebob/swarm-forge/blob/main/swarmforge/scripts/swarm_handoff.sh) (lines 1-6), the script is intentionally minimal:

```bash
#!/usr/bin/env zsh
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
exec bb "$SCRIPT_DIR/swarm_handoff.bb" "$@"

```

This wrapper pattern ensures consistent Babashka execution regardless of your current working directory. The real implementation lives in `swarm_handoff.bb`, which runs an 8-step pipeline for every handoff.

## The Handoff Pipeline: Step by Step

### Step 1: Determine Sender Role

The script identifies who is sending the handoff by reading the current worktree's role from the roles file via `handoff-lib`:

- **Function**: `sender-role` (lines 88-94)
- **Source**: reads Git configuration and role mappings

### Step 2: Validate Draft Location

Draft files must reside under `./tmp/` inside the sender's worktree. This constraint prevents cross-contamination between worktrees:

- **Function**: `require-worktree-tmp-draft!` (lines 99-102)
- **Fails fast** if the draft path violates this rule

### Step 3: Parse Draft Headers

Headers are processed line-by-line into a field map with syntax error collection:

- **Function**: `parse-draft` (lines 111-145)
- Supports **required fields**, **optional fields**, and **comment lines** (starting with `#`)

### Step 4: Enrich Missing Fields

The `prepare-headers` function (lines 89-95) injects defaults:

| Field | Default Source |
|-------|---------------|
| `priority` | `50` (hardcoded) |
| `commit` | Current HEAD short SHA via `git rev-parse` |

### Step 5: Run Validation Checks

Five validation categories run in sequence (main flow, lines 274-316):

- **`base-errors`** — required fields present (`type`, `to`, `priority`)
- **`validate-recipients`** — role exists, no duplicate recipients
- **`canonical-commit`** — commit SHA normalized via `git rev-parse`
- **`task-state-errors`** — task exists on board, not already "done"
- **`duplicate-errors`** — no identical active handoff already queued

All errors merge into a single report; any failure aborts with structured output.

### Step 6: Prepare the Payload

For `git_handoff` types, `write-handoff!` (lines 403-447):

1. Computes changed files via `commit-artifacts`
2. Builds a `.handoff` file in `.swarmforge/handoffs/outbox/`
3. Names files using pattern: `{priority}_{timestamp}_{sequence}_from_{sender}_to_{recipient}.handoff`

### Step 7: Handle Audit State

`submit-after-audit!` (lines 260-274) checks audit history:

- **If audited before**: immediate submission
- **If new**: creates pending-audit file, prompts user to run audit

### Step 8: Cleanup

On success: draft deleted, "HANDOFF QUEUED" message printed.

## Creating Valid Draft Files

Drafts are plain text with **YAML-like headers**. The `type` field determines processing rules.

### Example: Git Handoff Draft

Create under `./tmp/` of your worktree:

```bash
cat > ./tmp/api-refactor.handoff <<'EOF'
type: git_handoff
to: reviewer
priority: 40
task: refactor-user-service

# commit auto-filled from HEAD

EOF

```

Execute:

```bash
$ ./swarmforge/scripts/swarm_handoff.sh ./tmp/api-refactor.handoff
HANDOFF QUEUED: .swarmforge/handoffs/outbox/40_20231128T123456Z_000001_from_dev_to_reviewer.handoff

```

### Example: Note Handoff (Non-Code)

```bash
cat > ./tmp/deploy-notice.handoff <<'EOF'
type: note
to: ops
priority: 20
message: Production deploy scheduled for 02:00 UTC
EOF

```

```bash
$ ./swarmforge/scripts/swarm_handoff.sh ./tmp/deploy-notice.handoff
HANDOFF QUEUED: .swarmforge/handoffs/outbox/20_20231128T123456Z_000002_from_dev_to_ops.handoff

```

### Error Handling Example

Missing required fields produce structured error reports:

```bash
$ ./swarmforge/scripts/swarm_handoff.sh ./tmp/incomplete.handoff
HANDOFF INVALID: ./tmp/incomplete.handoff

Errors:
- Missing required header 'type'.
- Missing required header 'to'.
- Missing required header 'priority'.

```

## Cross-Shell Compatibility

Though the wrapper uses Zsh, it executes from any shell with Babashka installed:

```bash

# From Bash

bash -c "./swarmforge/scripts/swarm_handoff.sh ./tmp/my-feature.handoff"

# From Fish

bash ./swarmforge/scripts/swarm_handoff.sh ./tmp/my-feature.handoff

```

The `exec bb` pattern ensures the Babashka process replaces the shell entirely, eliminating subshell compatibility concerns.

## Required Headers by Handoff Type

| Header | `git_handoff` | `note` | Description |
|--------|-------------|--------|-------------|
| `type` | Required | Required | Discriminator for processing branch |
| `to` | Required | Required | Recipient role(s), comma-separated |
| `priority` | Required¹ | Required¹ | Integer 1-99, lower = higher priority |
| `task` | Required | Optional | Board task identifier |
| `message` | Optional | Required | Human-readable content |
| `commit` | Auto-filled | N/A | Git SHA for code handoffs |

¹ Defaults to `50` if omitted

## Key Source Files

| File | Purpose | Lines of Interest |
|------|---------|-----------------|
| [`swarmforge/scripts/swarm_handoff.sh`](https://github.com/unclebob/swarm-forge/blob/main/swarmforge/scripts/swarm_handoff.sh) | Zsh entry wrapper | 1-6 |
| `swarmforge/scripts/swarm_handoff.bb` | Core implementation | 74-115 (`-main`), 274-316 (validation) |
| `swarmforge/scripts/handoff_lib.bb` | Shared Git/role utilities | `sender-role`, `git-toplevel` |
| [`swarmforge/scripts/pack_board.sh`](https://github.com/unclebob/swarm-forge/blob/main/swarmforge/scripts/pack_board.sh) | Post-audit board update | Triggered after successful audit |
| [`swarmforge/scripts/done_with_current.sh`](https://github.com/unclebob/swarm-forge/blob/main/swarmforge/scripts/done_with_current.sh) | Task completion helper | Marks in-process task done |

## Summary

- **[`swarm_handoff.sh`](https://github.com/unclebob/swarm-forge/blob/main/swarm_handoff.sh)** is a thin wrapper that launches `swarm_handoff.bb` via Babashka
- **Draft files must live in `./tmp/`** of the sender's worktree
- **Validation runs 8 steps** from role detection through audit handling
- **Two primary handoff types**: `git_handoff` (code) and `note` (messages)
- **Auto-enrichment** fills `priority` (default 50) and `commit` (current HEAD)
- **Structured error reports** prevent malformed handoffs from entering the system

## Frequently Asked Questions

### Where must I save draft handoff files?

Draft files must be saved under `./tmp/` inside your current worktree. The `require-worktree-tmp-draft!` function (lines 99-102) enforces this to maintain isolation between worktrees. Attempting to handoff a file from outside this directory aborts with a clear error message.

### What happens if my draft has validation errors?

The script aggregates all validation failures—missing headers, invalid recipients, bad commit SHAs, duplicate handoffs—into a single error report prefixed with `HANDOFF INVALID`. No partial handoff is created; you must fix all errors and re-run.

### Does [`swarm_handoff.sh`](https://github.com/unclebob/swarm-forge/blob/main/swarm_handoff.sh) work without Zsh?

Yes. While the wrapper specifies `#!/usr/bin/env zsh`, the `exec bb` pattern means any shell can invoke it provided Babashka (`bb`) is installed and on your PATH. The Zsh dependency is for the launcher script only, not the core Babashka implementation.

### What is the difference between `git_handoff` and `note` types?

**`git_handoff`** transfers code changes: it computes file diffs from the specified commit, requires a `task` reference, and auto-fills the commit SHA. **`note`** sends unstructured messages without Git artifacts, requiring a `message` field instead. Both use the same validation pipeline but diverge during payload construction in `write-handoff!`.