v0.24.0 — A bare `copier update` destroyed the service it was meant to upgrade
v0.24.0 — A bare copier update destroyed the service it was meant to upgrade
Release date: 2026-08-07
Hotfix for a destructive defect present in v0.23.0 and every release
before it. Found by running copier update on a freshly generated service
instead of assuming it worked — the same method that produced every finding
in the last two releases.
What was wrong
v0.23.0 pinned --vcs-ref on every documented copier copy. It left
copier update unpinned: in copier.yml's _message_after_copy, in
the scaffold-update skill, and in rule 15-template-lifecycle.
Same root cause — Copier resolves an unpinned git source to the
highest-sorting tag, and the frozen v1.0.0–v1.12.0 audit snapshots sort
above every v0.x tag. Far worse consequence.
copier copy unpinned hands you a stale scaffold. copier update
unpinned rewrites a service you already have, backwards.
Measured on a real v0.23.0 service:
Bare copier update |
--vcs-ref=v0.23.0 |
|
|---|---|---|
| Files after | 435 | 627 |
| Files deleted | 582 | 0 |
.copier-answers.yml |
deleted | present |
The deleted answers file is the sharp edge. It is the record copier update reads, so once it is gone the service cannot recover on its own —
the operation removes the mechanism that would undo it.
And _message_after_copy was telling every adopter to run exactly that
form, as the last thing they read after scaffolding.
What changed
--vcs-refpinned on every documentedcopier update._message_after_copynow states the consequence, not just the command.scripts/check_adopter_scaffold_ref.pyextended to covercopier update
as well ascopier copy, verified in both directions.
Known follow-ons (scoped, not regressions)
- The
v1.xtag-sort collision is still unresolved. This is the
third defect it has produced —v0.22.0reaching nobody, the
copier copydocs, and nowcopier update— and the third fix that is
a pin. Pinning is correct and lands safely under any long-term option,
but it is mitigation, not resolution. The structural fixes (moving
the frozen snapshots out of the tag namespace, or advancing the active
line pastv1.12.0) need their own ADR, becauseagentic/rules/18and
ADR-014 both declare those tags immutable. Not decided unilaterally. - The
MIGRATION.mdrecovery procedure for an already-downgraded
service is un-rehearsed. It is derived from the mechanism (the
pre-update commit must exist, becausecopier updaterequires a clean
tree), not from a performed recovery. copier updateacross a real version gap is still unexercised.
Verified: the destructive unpinned case, and a pinned same-version
update proving non-destructiveness. An update that actually crosses two
releases and three-way-merges local modifications has not been run.- Everything carried forward from
v0.23.0: clock-allowlist brittleness,
ruffUP/B/Srulesets,mypy→pyright, shadow-lane precision
data.
Until the structural fix lands, assume any Copier invocation against
this repo needs an explicit ref, and expect the guard to be the thing that
remembers.
Verification
Both directions, on real generated services:
# unpinned — 627 -> 435 files, 582 deleted, answers file GONE
# pinned — 627 -> 627 files, 0 deleted, answers file PRESENT
Evidence in VALIDATION_LOG.md Entry 018.