# `zfs hold` proposal (NOT implemented)

> **Deferred by decision**, 2026-07-28. Dropped from `problem/RepairPlan.md` because it is not a
> minimal change and carries real breakage risk. Retained here for later review. Nothing in the
> implemented fix depends on it.

## Problem it would solve

The base-selection fallback and long-TTL anchor (see `problem/RepairPlan.md` Steps 2a-2f) make the
system *recover* from a lost resume point. Holds would make losing one *impossible*. The failure
mode is structural: `$arr->[-1]` (`ZFS_Utils.pm` ~line 1222) records the newest snapshot as the
resume point, which is always the shortest-retention class, so the recovery anchor self-destructs in
1-7 days. The long-TTL anchor (Step 2e) mitigates this; a hold removes it.

## Mechanism

`zfs hold <tag> <snapshot>` sets a user reference that makes `zfs destroy` fail until
`zfs release`d. Applied symmetrically:

- **Source**, after a successful send: `zfs hold sneakernet <resume snapshot>`; release the previous
  run's hold only once the new one is placed.
- **Target**, after a successful receive: the same, on the snapshot the source will resume from.

Both sides then guarantee the sync point survives arbitrary gaps — a 53-day outage becomes a large
incremental instead of a full re-seed.

## Why it was deferred

1. **Both sides must participate.** A hold on the source alone buys nothing if the target's
   `cleanSnaps` deletes its copy. The target is air-gapped and runs only when a technician powers it
   on, so target-side holds depend on the code-deployment procedure being solved first.
2. **`cleanSnaps` starts failing.** `zfs destroy` errors on held snapshots. Arguably the point, but
   it turns a clean cron run into a noisy one; `cleanSnaps` needs to learn to distinguish
   "held — skip quietly" from a genuine error, or every run emails failures.
3. **Lost or delayed drives leak.** A hold with no successful follow-up pins its snapshot forever,
   and the space it holds down grows with the gap. Needs a max-age escape hatch and an operator
   force-release path.
4. **Recursive semantics.** Whole-tree sends use `-R`; a matching `zfs hold -r` must be released in
   the right order or holds orphan on datasets that are later destroyed — exactly what happened to
   `iscsi/web`, which would have left a permanently held snapshot on a recreated dataset.
5. **Ordering hazard.** Release-then-hold leaves a window where nothing is pinned; hold-then-release
   leaves two pinned. The latter is correct but needs care on failure paths.

## Alternative considered: `zfs bookmark`

The purpose-built primitive — near-zero space, cannot be "used up", survives deletion of the
snapshot it came from, and no release bookkeeping. It has none of problems 2-5 above.

**But it only works with `zfs send -i`, not `-I` or `-R`.** Adopting it means giving up replication
of intermediate snapshots, so the target would receive only endpoint snapshots and lose the
monthly/weekly/daily history it currently mirrors. That is a redesign of the replication strategy,
not a drop-in fix, and it would change what the offsite copy actually contains.

## Recommendation

Land the base-selection fallback and long-TTL anchor first, run one full cycle, and measure. The
long-TTL anchor alone would have prevented the 2026-07-27 incident, and it is source-side with no
operator involvement. If it holds up in practice, holds buy little for their cost. Revisit only if a
real resume-point loss recurs despite it.

If revisited, sequence it as: source-side holds first (self-contained, reversible, exercises the
`cleanSnaps` interaction with no target dependency), then target-side once the deployment procedure
exists.
