# How Branch Sync Handles Custody Recovery for Terminal Runs with Unpublished Pipeline Commits

> Discover how branch sync handles custody recovery for terminal runs with unpublished pipeline commits. Learn about the guarded recover_custody action for seamless branch management.

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

---

**When a run terminates in a terminal state without publishing its pipeline-generated commits, the branch sync subsystem locks the branch in custody and offers a guarded `recover_custody` action that fast-forwards the branch to the preserved pipeline head while recording the recovery in the database.**

In the `kunchenguid/no-mistakes` repository, pipeline runs that end terminally can leave unpublished commits stranded in the local gate. The **branch sync custody recovery** mechanism ensures these commits are never lost by preserving the pipeline state and providing a safe path to return branch control to the operator.

## Understanding Terminal Runs and the Custody State

When a run enters a terminal state—such as **failed** or **cancelled**—without publishing its pipeline-generated commits, those commits remain preserved in the local gate. The system marks the branch as being in *custody* of the run, blocking further local operations that could accidentally discard the unpublished work.

This custody state prevents data loss by freezing the branch until the operator explicitly recovers the unpublished commits or acknowledges the terminal state.

## State Detection in internal/branchsync/sync.go

The detection logic resides in [`internal/branchsync/sync.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/branchsync/sync.go). The system defines the `StateCustodyReturned` constant and monitors run status to identify when custody recovery is required.

When the code detects `run.Status.IsTerminal() && !run.HasPublishedPipelineCommits()`, it triggers the custody state by setting:

```go
state.NextAction = &NextAction{
    Code:    "recover_custody",
    Command: "no-mistakes axi sync --recover",
}
state.Safety = "custody_returned"

```

This assignment occurs at lines 41-45 in the sync implementation, ensuring the state machine transitions correctly when unpublished work remains in the gate.

## The Recovery Workflow

### CLI User Guidance

The CLI surfaces this state to users through [`internal/cli/sync.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/cli/sync.go). When custody is detected, the interface prints a specific instruction:

```

Run ended without publishing its pipeline commits; recover custody with `no-mistakes sync --recover`

```

This prompt appears at lines 262-266, ensuring operators know exactly which command to execute next.

### Executing the Recover Command

Running `no-mistakes axi sync --recover` invokes the `Recover` function in [`internal/branchsync/sync.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/branchsync/sync.go). This function performs several critical operations:

1. **Validates worktree cleanliness** (or accepts `--keep-local` to preserve the current head)
2. **Fast-forwards the branch** to the preserved pipeline head
3. **Writes the custody timestamp** to `custody_returned_at` in the run record via [`internal/db/run.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/db/run.go) (lines 244-251)
4. **Returns branch ownership** to the operator for fresh runs

### Safety Checks and Abort Conditions

The recovery path includes rigorous validation in [`internal/branchsync/sync.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/branchsync/sync.go) (lines 512-578). It aborts with specific error codes if assumptions are violated:

- **`blocked_recover_dirty`**: The local branch contains uncommitted changes
- **`blocked_recover_assumptions_changed`**: The preserved commits have changed since the run terminated
- **Gate branch modification**: The gate branch was altered during the recovery attempt

Each condition generates a clear blocked plan explaining why recovery cannot proceed, preventing accidental data loss.

## Database Persistence and Agent Guidance

Upon successful recovery, the system updates the database through [`internal/db/run.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/db/run.go), persisting the `custody_returned_at` timestamp to the run record. The `state.Safety` field is set to `"custody_returned"`, and the CLI reports: *"custody returned; the branch is yours – start a fresh run when ready"* (lines 266-270 in [`internal/cli/sync.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/cli/sync.go)).

Additionally, [`internal/skill/skill.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/skill/skill.go) (lines 225-291) embeds this recovery flow into agent guidance text, ensuring automated systems recommend `no-mistakes axi sync --recover` when detecting terminal runs with unpublished commits.

## Practical Usage Examples

To recover from a terminal run with unpublished commits:

```bash

# Run ends terminally; system hints at recovery

$ no-mistakes axi run --intent "fix bug"

# CLI output:

#   Run ended without publishing its pipeline commits; recover custody with `no-mistakes sync --recover`

# Recover custody and fast-forward to preserved head

$ no-mistakes axi sync --recover

# → custody returned; the branch is yours – start a fresh run when ready

```

To keep the current local head without moving it:

```bash
$ no-mistakes axi sync --recover --keep-local

```

## Summary

- Terminal runs with unpublished pipeline commits trigger a **custody state** that preserves commits in the local gate and prevents local operations
- The **branch sync** subsystem detects this via `StateCustodyReturned` in [`internal/branchsync/sync.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/branchsync/sync.go) and offers a `recover_custody` action
- Recovery requires a clean worktree (or the `--keep-local` flag) and fast-forwards the branch to the preserved pipeline head
- The system records recovery via the `custody_returned_at` timestamp in [`internal/db/run.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/db/run.go)
- Multiple safety checks prevent data loss, with specific error codes like `blocked_recover_dirty` for blocked recovery scenarios

## Frequently Asked Questions

### What happens to unpublished commits when a run fails or is cancelled?

When a run terminates in a terminal state without publishing its pipeline-generated commits, those commits remain preserved in the local gate. According to the source code in [`internal/branchsync/sync.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/branchsync/sync.go), the branch enters a custody state that prevents local operations from overwriting the unpublished work until custody is explicitly recovered via the `Recover` function.

### How do I know if a branch is in custody and needs recovery?

The CLI displays a specific message generated in [`internal/cli/sync.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/cli/sync.go) (lines 262-266): *"Run ended without publishing its pipeline commits; recover custody with `no-mistakes sync --recover`"*. This appears when the state machine detects `StateCustodyReturned` and sets `NextAction` to `recover_custody` with the command `no-mistakes axi sync --recover`.

### Can I recover custody if I have local uncommitted changes?

No, the recovery aborts with error code `blocked_recover_dirty` unless you use the `--keep-local` flag. The `Recover` function in [`internal/branchsync/sync.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/branchsync/sync.go) validates worktree cleanliness at lines 512-578 before allowing the fast-forward to the preserved pipeline head, ensuring you do not accidentally lose local work.

### Where does the system record that custody has been returned?

The `Recover` function writes to the `custody_returned_at` field in the run record via [`internal/db/run.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/db/run.go) (lines 244-251). This timestamp confirms the branch has been returned to the operator and is safe for fresh runs, while the CLI sets `state.Safety` to `"custody_returned"` to reflect the completed recovery.