Documents / Issues / issue-9bbca6c0f434
`tag rename --merge` onto itself deletes the tag and leaves every document carrying it
Data corruption reachable from a plausible command, shipped in 0.7.0.
Class: incorrect · Severity: material
Flow: arch-ccfcceeb35eb · Step: docir tag rename X X --merge
Question: None · Frequency: any self-merge; also tag rename X X without --merge, same path
Finding¶
A self-merge deleted the tag from the registry and left every document still carrying it, while reporting success.
What happens today¶
OBSERVED and FIXED the same day. tag rename auth auth --merge returned {"renamed":["auth","auth"],"documents":["adr-0001"]}, tag list was empty, and the document still read tags: [auth] — the corpus was left in exactly the unknown-tag state check reports. uow.tags.delete(old) runs unconditionally, and the document rewrite maps old -> new, a no-op when they are the same string, so nothing restored the entry.
Impact¶
Data corruption reachable from a plausible command, shipped in 0.7.0. Self-inflicted: introduced by the issue-cc61d038cf8f merge four commits earlier, whose tests covered merging two different tags and never the degenerate case.
Proposed default¶
Reject old == new before opening the unit of work.
Resolution¶
FIXED 2026-07-29. rename rejects old == new with a ValidationError before the unit of work opens, for both the plain and the --merge form. Pinned by two tests that assert the registry and the document survive and that check reports no unknown-tag. The general lesson, and the reason the delta pass earns its keep: a feature added to close a gap is itself new surface, and its own degenerate cases are unexamined. The issue-cc61d038cf8f tests asked "does merging two tags work?" and never "what if they are the same tag?".
Actors affected¶
- repository maintainer
- AI coding agent
Evidence¶
src/docir/modules/tags/application/services/tag_service.pyref-1509d5dbb4c3 (discovery probe log)
Migrated from the discovery gap register (GAP-048); the register itself now lives in this store.