# How Custody Recovery Works for Terminal Runs with Unpublished Pipeline Commits in no-mistakes

> Learn how custody recovery works for terminal runs with unpublished pipeline commits in no-mistakes. Safely sync and reconcile preserved pipeline heads with your local worktree using axi sync recover.

- Repository: [Kun Chen/no-mistakes](https://github.com/kunchenguid/no-mistakes)
- Tags: how-to-guide
- Published: 2026-07-19

---

**When a no-mistakes run terminates without publishing its pipeline commits, the branch enters a custody state that blocks new work until you run `axi sync --recover`, which safely reconciles the preserved pipeline head with your local work‑tree using a strict safety matrix.**

The **no-mistakes** system protects your branch state by placing it in custody whenever a run ends in a terminal state—completed, failed, or cancelled—while pipeline commits remain unpublished. This custody mechanism stores the pipeline head in a **local gate** (a bare repository under `NM_HOME`) and prevents further work until custody is formally returned through a controlled recovery process.

## Understanding the Custody State

Custody acts as a safety lock. When a terminal run leaves commits unpublished, the branch cannot simply continue; the preserved pipeline head must be reconciled with your local work‑tree. The recovery flow is implemented in [`internal/branchsync/sync.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/branchsync/sync.go) and triggered via the `axi sync --recover` command or the TUI “recover custody” action.

The system evaluates the relationship between your current work‑tree and the preserved pipeline head **P** before allowing any state changes.

## The Recovery Decision Matrix

The recovery algorithm follows a strict decision matrix that guarantees data safety. The default path fast‑forwards when safe, while the `--keep-local` flag preserves your current position at the cost of leaving the pipeline commits dangling.

| Work‑tree relation to preserved head **P** | Default (`--keep-local` off) | `--keep-local` enabled |
|---|---|---|
| **equal** | Anchor locally, return custody | Anchor locally, return custody |
| **ahead** | Anchor locally, return custody | Anchor locally, return custody |
| **behind** (clean) | Fast‑forward to **P**, return custody | Return custody at current head |
| **behind** (dirty) | **Fail** – requires clean work‑tree | Return custody at current head |
| **diverged** | **Fail** – manual reconciliation required | Return custody at current head |
| **gate missing** | **Fail** – cannot verify commits | **Fail** |

## Step‑by‑Step Recovery Implementation

The recovery logic spans lines 404–450 of [`internal/branchsync/sync.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/branchsync/sync.go) and operates through four distinct phases.

### 1. State Inspection

The `inspect()` function (lines 333–368) gathers local Git state, the active run record, and determines whether the run is *pipeline_owned* (indicating unpublished pipeline commits). This function establishes the baseline for all subsequent decisions.

### 2. Detecting Recoverable Runs

`classifyPipelineOwned()` (lines 724–790) identifies terminal runs eligible for custody recovery. When detected, it sets `NextAction.Code = "recover_custody"`, signaling that the branch is locked and requires explicit user intervention.

### 3. Executing Recovery

The `Recover()` function (lines 462–526) implements the decision matrix:

- **Equal or Ahead**: When **P** is already reachable, `anchorReachablePreserved()` anchors the commits locally, followed by `finishRecover()` (lines 666–681).
- **Behind (clean)**: `recoverFastForward()` (lines 670–700) advances the branch to **P** before returning custody.
- **Keep‑Local Mode**: `recoverKeepLocal()` (lines 688–730) performs an atomic compare‑and‑swap using `git update-ref … <old> <new>` to move the gate branch to your current local head without touching the work‑tree.

### 4. Finalizing Custody Return

`finishRecover()` (lines 714–727) persists a `custody_returned_at` timestamp via `DB.SetRunCustodyReturned` and transitions the state to `StateCustodyReturned`. The CLI surfaces this through guidance strings defined in [`internal/cli/axi_guidance.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/cli/axi_guidance.go) (lines 24–31) and the skill body in [`internal/skill/skill.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/skill/skill.go) (lines 225–292).

## Safety Guarantees and Atomic Operations

The custody recovery system enforces several critical safety constraints:

- **Terminal‑only recovery**: Active runs block recovery with code `blocked_recover_run_active`.
- **Anchoring**: Preserved commits are anchored locally before any gate modification, preventing loss even if the gate is rewritten.
- **Clean work‑tree requirement**: Fast‑forward operations require a clean work‑tree to prevent overwriting uncommitted changes.
- **Atomic updates**: Gate updates use Git’s atomic compare‑and‑swap to eliminate race conditions during concurrent pushes.

## Practical Recovery Commands

Detect custody state and return custody using the no-mistakes CLI:

```bash

# Check if branch is in custody

no-mistakes axi status

# Output shows `code: recover_custody` with next‑action command

# Standard recovery: fast-forward to preserved head

no-mistakes axi sync --recover

# Branch fast-forwards to pipeline head P

# Run stamped with custody_returned_at

# Start fresh work:

no-mistakes axi run --intent "implement feature X"

# Keep current local head (bypass fast-forward)

no-mistakes axi sync --recover --keep-local

# Gate branch moves atomically to local head

# Preserved commits remain reachable via hidden refs

# Later use `no-mistakes rerun` to validate preserved head

```

## Summary

- Custody locks a branch when a terminal run leaves pipeline commits unpublished, storing the head in a local gate under `NM_HOME`.
- Recovery follows a strict matrix in [`internal/branchsync/sync.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/branchsync/sync.go) that evaluates work‑tree cleanliness and divergence.
- The `--keep-local` flag bypasses fast‑forwarding, preserving dirty work‑trees through atomic gate updates.
- All recovery paths conclude with `finishRecover()` persisting a `custody_returned_at` timestamp and unlocking the branch.
- Safety mechanisms include atomic `git update-ref` operations, mandatory anchoring, and clean work‑tree verification.

## Frequently Asked Questions

### What triggers a custody state in no-mistakes?

A custody state triggers when a run reaches a terminal status—completed, failed, or cancelled—without publishing its pipeline commits to the remote. The system stores the pipeline head in a local gate and marks the run as *pipeline_owned*, blocking new runs until custody is returned via `axi sync --recover`.

### How does the `--keep-local` flag change recovery behavior?

Without `--keep-local`, the default path fast‑forwards clean work‑trees to the preserved pipeline head **P**, failing if the tree is dirty or diverged. With `--keep-local`, the gate branch moves atomically to your current local head regardless of divergence or dirtiness, leaving the work‑tree untouched but potentially leaving **P** reachable only through hidden refs.

### What happens if my work-tree is dirty during recovery?

In default mode, a dirty work‑tree blocks recovery with a failure message instructing you to commit or stash changes first. With `--keep-local`, dirty work‑trees are allowed; the gate updates to your current head without modifying working directory files, though you must manually reconcile changes later.

### Where is the custody recovery logic implemented?

The core algorithm resides in [`internal/branchsync/sync.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/branchsync/sync.go), specifically the `Recover()` function (lines 462–526) and its helpers `recoverFastForward()` and `recoverKeepLocal()`. Supporting infrastructure includes [`internal/db/run.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/db/run.go) for timestamp persistence, [`internal/cli/axi_guidance.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/cli/axi_guidance.go) for user messaging, and [`internal/tui/branch_sync.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/tui/branch_sync.go) for the interactive recovery dialog.