---
description: Follow Verso's exact plan, persistent transaction, recovery, atomic push, and CI handoff boundaries.
---

# Release workflow

Verso calculates one exact file plan, then records progress through explicit local stages and one
remote transaction. The persisted plan makes an interrupted release inspectable and recoverable
without recomputing its intended output.

## Before any write

Verso loads one release-group config, discovers its members, verifies that their versions are
consistent, resolves one target version, and calculates every file's actual before and after content.
A full release also calculates the changelog, commit message, tag, hooks, and atomic push. A bump plan
contains only version-file changes and version hooks.

```sh
verso --version 2.0.0 --dry-run
verso bump minor --dry-run
```

Dry-run renders that exact plan, including full-file before/after diffs, without writing files,
running hooks, creating a transaction journal, or executing mutating Git commands.

Add `--group core` when working with a named release group.

For execution, Verso checks the worktree policy and planned paths, rejects an existing target tag,
and takes a repository transaction lock. Only one Verso transaction may be active per Git repository.

## Persisted stages

After the first file-change confirmation, Verso stores the plan and progress under Git metadata and
uses atomic file replacement. The release stages are:

```text
planned
  -> before_version -> apply exact files -> files-applied -> after_version
  -> before_commit -> git add/commit -> committed -> after_commit
  -> before_tag -> annotated tag -> tagged -> after_tag
  -> before_push -> git push --atomic -> pushed -> after_push
  -> clear transaction
```

`verso bump` follows only `planned -> files-applied`, runs `before_version` and `after_version`, then
clears the transaction. It does not change the changelog, stage files, create a commit or tag, or
push.

The release confirmations before files, commit, tag, and push default to yes. A target that is not
greater than the current version has an earlier confirmation that defaults to no. `--yes` accepts all
of them.

## Status, resume, and abort

```sh
verso status [--json]
verso resume [--retry-hook | --skip-hook]
verso abort [--force]
```

`status` is read-only. It reports the operation, release group, stage, version pair, changed-file
count, interrupted hook, and whether abort is still allowed.

`resume` first reconciles the journal with the exact file contents, `HEAD`, and tag. It skips completed
hooks and confirmations, then continues from the recorded stage. A hook that was active when the
process stopped has an unknown outcome: inspect its side effects, then explicitly use `--retry-hook`
or `--skip-hook`.

If another mutating release or bump is started while a transaction is active, Verso shows its group,
version pair, and stage, then prompts to resume or abort it. Recovery completes that invocation; it
does not continue into the newly requested release.

`abort` is conservative. It compare-and-deletes only the exact tag object Verso created, soft-resets
only the expected release commit, unstages only planned paths, and restores a file only when it still
matches the plan's after content. It refuses an unexpected `HEAD`, moved tag, or later file edit
rather than overwriting unrelated work. Once the release commit exists, abort also checks the pinned
remote and refuses an unowned target tag or a branch that already contains the release commit.

<Callout variant="danger" title="Push is the recovery boundary">
  Once push starts, abort is disabled because the remote outcome may be unknown. `verso resume`
  verifies the persisted tag and commit against the pinned remote, then finishes or retries the same
  atomic push when safe. Partial or mismatched refs require manual recovery.

If exact recovery is no longer possible, inspect the repository and remote first. `verso abort
  --force` discards only the active journal; it deliberately leaves files, commits, tags, and remote
refs untouched.

</Callout>

Resume keeps using the push URL, commit, and tag persisted when the transaction started, even if
local `HEAD` advances after push begins.

## Failure and cancellation behavior

Expected file, hook, commit, and tag failures before push trigger a best-effort local abort. Release
hook failures are checked against the pinned remote first; if that remote cannot be inspected, Verso
preserves the journal instead of assuming the hook did not publish. A crash, user cancellation at a
later confirmation, or an ambiguous state preserves the journal so the user can choose `resume` or
`abort` while push has not started. A push failure preserves the journal for `resume` only.

| Stopping point                       | Persisted state and next action                                                         |
| ------------------------------------ | --------------------------------------------------------------------------------------- |
| Before initial file confirmation     | No transaction and no file change                                                       |
| Files applied / before commit        | Files remain unstaged; `resume` commits or `abort` restores them                        |
| Commit created / before tag          | Expected release commit remains; `resume` tags or `abort` soft-resets and restores      |
| Tag created / before push            | Local commit and tag remain; `resume` pushes or `abort` removes the expected local work |
| Atomic push failed                   | Local commit, tag, and journal remain; fix or inspect the remote, then `resume`         |
| Push succeeded / `after_push` failed | Local and remote refs remain; only `resume` is allowed                                  |

Cleanup never uses a hard reset. Restore, unstage, reset, tag-delete, journal, or remote-verification
errors are reported instead of being hidden.

## Hooks and CI handoff

Hooks are arbitrary project shell commands. Dry-run prints their names and commands but executes none.
Pass secrets through the environment rather than storing them in config, keep hooks retry-safe, and
prefer CI for destructive or credentialed publication. `after_push` is a follow-up boundary: it can be
resumed, but it cannot undo remote refs. After an interrupted hook, confirm that its old process has
stopped before retrying, skipping, or aborting the transaction.

Registry publication, GitHub Releases, binary builds, and deployment remain the responsibility of
tag-triggered CI.

Before enabling automation, capture both checks in the target repository:

```sh
verso doctor --json
verso --version <next-version> --dry-run --json
```

Add `--group <NAME>` to both commands when automating a named release group.

## Atomic push details

<details>
  <summary>Show the exact refs pushed by Verso</summary>

Verso resolves the current branch's configured remote and merge ref, then pushes only these two refs:

```text
<release-commit-oid>:refs/heads/<upstream-branch>
<tag-object-oid>:refs/tags/<release-tag>
```

The command uses `git push --atomic`; unrelated local tags are not included. The remote must support
atomic pushes, so it accepts both refs or neither.

</details>
