# How `swarm_handoff.sh` Audit Gate Enforces Two-Call Verification for Git Handoffs

> Learn how swarm_handoff.sh audit gate enforces two-call verification for Git handoffs by requiring a signed audit file before proceeding. Secure your code reviews.

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

---

**The [`swarm_handoff.sh`](https://github.com/unclebob/swarm-forge/blob/main/swarm_handoff.sh) audit gate enforces two-call verification by requiring a signed audit file to exist and match the current draft before any Git handoff can proceed.**

In the `unclebob/swarm-forge` repository, handoffs between AI agents follow a strict **two-call verification** protocol. The audit gate in [`swarm_handoff.sh`](https://github.com/unclebob/swarm-forge/blob/main/swarm_handoff.sh) ensures no handoff executes blindly—every transfer requires explicit human or system confirmation through a recorded audit.

## How the Two-Call Verification Flow Works

The verification splits into two distinct invocations of [`swarm_handoff.sh`](https://github.com/unclebob/swarm-forge/blob/main/swarm_handoff.sh). Each call has a single purpose, and the second call fails unless the first completed successfully with an identical draft.

### Call 1: Create the Audit Record

When you first invoke [`swarm_handoff.sh`](https://github.com/unclebob/swarm-forge/blob/main/swarm_handoff.sh) with a draft:

```bash
swarm_handoff.sh my-draft.handoff

```

The wrapper executes `swarm_handoff.bb`, which writes an **audit file** to:

```

.<project-root>/.swarmforge/handoffs/audit_pending/<sender-hash>/<task-hash>.edn

```

The helper functions `audit-pending-dir`, `sender-audit-dir`, and `audit-file` construct this deterministic path in `swarmforge/scripts/swarm_handoff.bb` (lines 41–54). The `write-audit!` function (lines 69–74) persists the candidate draft content plus a timestamp.

### Call 2: Verify and Submit

Running the **same command a second time** triggers the verification gate:

```bash
swarm_handoff.sh my-draft.handoff

```

The script now:

1. Locates pending audits via `sender-audit-files` (lines 55–61)
2. Reads the stored audit with `read-audit`
3. Compares the `candidate` field against the current draft **byte-for-byte**

If they match, the handoff proceeds. If the audit is missing or the candidate differs, the script aborts with an explicit error forcing a fresh audit.

## Concurrency Protection with File Locking

Multiple processes cannot race on audit files. The `with-audit-lock` macro (lines 75–84) acquires an **exclusive file-channel lock** on a `.lock` file in the audit-pending directory before any read or write.

This guarantees atomicity: even with parallel [`swarm_handoff.sh`](https://github.com/unclebob/swarm-forge/blob/main/swarm_handoff.sh) invocations, only one process examines or modifies an audit at any moment.

## What Happens When the Draft Changes

The verification is **content-hash strict**. Any modification between calls invalidates the audit:

```bash

# 1️⃣ First call — audit created

$ swarm_handoff.sh my-draft.handoff

# Draft accidentally modified

$ echo "bug fix" >> my-draft.handoff

# 2️⃣ Second call — verification fails

$ swarm_handoff.sh my-draft.handoff

# Error: audit missing or draft changed; run audit again

```

The `audit-and-submit-git-handoff` function orchestrates this behavior, first creating an audit if absent, then strictly verifying before Git operations execute.

## Test Coverage and Implementation Details

The test suite in `test/swarmforge/handoff_test.clj` (lines 719–795) validates the complete flow:

- Successful handoff after matching audit
- Rejection when audit is missing
- Rejection when candidate differs
- Proper lock acquisition and release

These tests confirm the two-call rule is **enforced at the code level**, not merely documented.

| Component | Location | Purpose |
|-----------|----------|---------|
| Shell wrapper | [`swarmforge/scripts/swarm_handoff.sh`](https://github.com/unclebob/swarm-forge/blob/main/swarmforge/scripts/swarm_handoff.sh) | Launches Babashka script |
| Audit logic | `swarmforge/scripts/swarm_handoff.bb` | Path construction, locking, verification |
| Protocol spec | [`swarmforge/handoff-protocol.md`](https://github.com/unclebob/swarm-forge/blob/main/swarmforge/handoff-protocol.md) | Documentation of audit semantics |
| Test suite | `test/swarmforge/handoff_test.clj` | Behavioral validation |

## Summary

- **Two-call verification** splits handoff into *audit creation* and *audit verification*
- **Content comparison** ensures the draft remains unchanged between calls
- **File locking** prevents race conditions on shared audit files
- **Deterministic paths** in `audit-pending/<sender-hash>/<task-hash>.edn` enable reliable lookup
- **Explicit failure** on mismatch forces re-audit rather than silent success

## Frequently Asked Questions

### What happens if I run [`swarm_handoff.sh`](https://github.com/unclebob/swarm-forge/blob/main/swarm_handoff.sh) only once?

The handoff does not execute. The first call creates an audit record and exits. You must invoke the command a second time with the identical draft to trigger the actual Git handoff.

### Can two different users share an audit?

No. Audit paths incorporate the **sender hash**, isolating each user's pending audits. Only the same sender can verify their own audit record.

### Does the audit timestamp affect verification?

No. The verification compares only the `candidate` field containing the draft content. The timestamp exists for logging and TTL purposes but does not influence the match check.

### Where is the two-call rule documented?

The semantics are defined in [`swarmforge/handoff-protocol.md`](https://github.com/unclebob/swarm-forge/blob/main/swarmforge/handoff-protocol.md) and implemented in `swarmforge/scripts/swarm_handoff.bb`. The `audit-and-submit-git-handoff` function embodies the enforcement logic.