diff --git a/tests/custom-html-bkp-bad/ops.py b/tests/custom-html-bkp-bad/ops.py
new file mode 100644
index 0000000..f6db098
--- /dev/null
+++ b/tests/custom-html-bkp-bad/ops.py
@@ -0,0 +1,19 @@
+"""custom-html-bkp-bad — lifecycle ops for bad-backup/bad-restore RED canaries.
+
+Intentionally has NO pre_backup hook: the marker is never seeded before backup,
+so the backup snapshot has no ci-marker.txt. pre_restore writes "mutated" so that if
+restore DOES bring back the snapshot, the marker is gone/still-mutated → test fails.
+"""
+
+from __future__ import annotations
+
+from harness import lifecycle
+
+MARKER_PATH = "/usr/share/nginx/html/ci-marker.txt"
+
+
+def pre_restore(domain: str, meta: dict) -> None:
+ """Write 'mutated' to the marker before restore runs. If restore brings back the
+ snapshot (which has no marker — never seeded by pre_backup), the marker ends up
+ MISSING or 'mutated' after restore → test_restore_returns_state FAILS → restore=RED."""
+ lifecycle.exec_in_app(domain, ["sh", "-c", f"echo mutated > {MARKER_PATH}"])
diff --git a/tests/custom-html-bkp-bad/test_restore.py b/tests/custom-html-bkp-bad/test_restore.py
new file mode 100644
index 0000000..ce1b924
--- /dev/null
+++ b/tests/custom-html-bkp-bad/test_restore.py
@@ -0,0 +1,25 @@
+"""custom-html-bkp-bad — RESTORE assertion (bad-restore RED canary).
+
+pre_restore seeds 'mutated' to ci-marker.txt. The backup snapshot has no ci-marker.txt
+(never seeded by pre_backup). After restore, the marker is either MISSING or 'mutated' —
+never 'original' — so this assertion FAILS → restore tier RED.
+"""
+
+import os
+import sys
+
+sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "runner"))
+from harness import lifecycle # noqa: E402
+
+MARKER_PATH = "/usr/share/nginx/html/ci-marker.txt"
+
+
+def test_restore_returns_state(live_app):
+ result = lifecycle.exec_in_app(
+ live_app, ["sh", "-c", f"cat {MARKER_PATH} 2>/dev/null || echo MISSING"]
+ ).strip()
+ assert result == "original", (
+ f"restore did not return the pre-mutation (backed-up) state: got {result!r}. "
+ "Expected 'original'. The backup had no marker (not seeded by pre_backup), so "
+ "restore cannot recover it — this is the intended failure for the bad-restore RED canary."
+ )
diff --git a/tests/regression/test_canaries.py b/tests/regression/test_canaries.py
index b146e54..1f54efd 100644
--- a/tests/regression/test_canaries.py
+++ b/tests/regression/test_canaries.py
@@ -141,14 +141,15 @@ _BAD_BACKUP = {
_BAD_RESTORE = {
"id": "bad-restore",
- "recipe": "custom-html",
- "src": "recipe-maintainers/custom-html",
- # Pin: regression-bad-restore @ 2026-06-02 — backup captures /usr/share/nginx/html/.backup-data/
- # (a subdir NOT containing ci-marker.txt). Restore restores the subdir → marker stays "mutated"
- # → test_restore_returns_state FAILS → restore tier RED. install+upgrade+backup PASS.
- "ref": "7e03499ff17b343b622b510efcfc44add9651065",
+ "recipe": "custom-html-bkp-bad",
+ "src": "recipe-maintainers/custom-html-bkp-bad",
+ # Pin: custom-html-bkp-bad main @ 2026-06-02 (b6fe99de).
+ # No pre_backup hook → marker never seeded before backup → snapshot has no ci-marker.txt.
+ # pre_restore writes "mutated". After restore, marker is MISSING or "mutated" → FAIL → restore=RED.
+ # install+backup PASS; upgrade=skip (no version tags yet).
+ "ref": "b6fe99de41601f9e51bc7ea5b6072f0c3f56cdc3",
"expected_green": False,
- "stages": "install,upgrade,backup,restore,custom",
+ "stages": "install,backup,restore,custom",
"failing_tier": "restore",
"passing_tiers_before": ["install", "backup"],
"stage_pass_checks": [],