The unused-tags cleanup tool deleted fandom tags the preview correctly excluded — real operator data loss (2026-06-08). Root cause: the unused-tag predicate was written twice — once in the preview (find_unused_tags) and again inline in prune_unused_tags (its COUNT and DELETE). An earlier fix patched only the preview; the delete kept the narrower predicate (image_tag/series_page only, missing the fandom + series_chapter exclusions), so it removed fandoms the preview had hidden. Deleting a fandom SET-NULLs its characters' fandom_id.
What
Extract _unused_tag_conditions() as the single source of truth; the preview sample, the dry-run count, and the live DELETE all consume it. No second copy left to drift.
Add a regression test that exercises the delete path (not just the preview): asserts dry-run count == committed delete count == 1 and that a fandom referenced by a used character + a chaptered series both survive. This is the gap that let the broken delete pass green CI before.
Notes
The deployed instance still has the broken delete until redeployed; don't run the cleanup tool on it until then.
Lesson banked: preview/dry-run and the destructive apply must share one predicate, and the apply path must be tested directly.
## Why
The unused-tags cleanup tool deleted fandom tags the preview correctly excluded — real operator data loss (2026-06-08). Root cause: the unused-tag predicate was written **twice** — once in the preview (`find_unused_tags`) and again inline in `prune_unused_tags` (its COUNT and DELETE). An earlier fix patched only the preview; the delete kept the narrower predicate (image_tag/series_page only, missing the fandom + series_chapter exclusions), so it removed fandoms the preview had hidden. Deleting a fandom SET-NULLs its characters' `fandom_id`.
## What
- Extract `_unused_tag_conditions()` as the single source of truth; the preview sample, the dry-run count, **and** the live DELETE all consume it. No second copy left to drift.
- Add a regression test that exercises the **delete** path (not just the preview): asserts dry-run count == committed delete count == 1 and that a fandom referenced by a used character + a chaptered series both survive. This is the gap that let the broken delete pass green CI before.
## Notes
- The deployed instance still has the broken delete until redeployed; don't run the cleanup tool on it until then.
- Lesson banked: preview/dry-run and the destructive apply must share one predicate, and the apply path must be tested directly.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
The fandom/chapter exclusions added in fb05c5e only touched find_unused_tags
(the preview SAMPLE). prune_unused_tags re-implemented the predicate inline for
the dry-run COUNT and the live DELETE with only the image_tag + series_page
checks — so the preview showed a safe list of names while the delete removed
every fandom (and chaptered series). Operator-flagged 2026-06-08: real data loss
— assigned fandoms deleted, their characters SET-NULLed.
Extract _unused_tag_conditions() as the single source of truth and use it for
the preview, the count, AND the delete, so they can never diverge again. Added a
prune-commit test asserting the LIVE delete spares a character's fandom and a
chaptered series.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The character pointing at the fandom had no image associations, so it was
itself unused and inflated the dry-run count to 2. Tag it on a real image so
it is used (the real-world shape) — the fandom survives via a live character.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Why
The unused-tags cleanup tool deleted fandom tags the preview correctly excluded — real operator data loss (2026-06-08). Root cause: the unused-tag predicate was written twice — once in the preview (
find_unused_tags) and again inline inprune_unused_tags(its COUNT and DELETE). An earlier fix patched only the preview; the delete kept the narrower predicate (image_tag/series_page only, missing the fandom + series_chapter exclusions), so it removed fandoms the preview had hidden. Deleting a fandom SET-NULLs its characters'fandom_id.What
_unused_tag_conditions()as the single source of truth; the preview sample, the dry-run count, and the live DELETE all consume it. No second copy left to drift.Notes
🤖 Generated with Claude Code