Repository navigation
X1: refuse occupied write destinations before doing the work - #217
Merged
Merged
Conversation
A write's destination collision was only ever found after the work: a
CBR was fully repacked before `_create_zipfile` noticed its CBZ twin
already existed, and two archives in one batch converging on the same
name were decided by whichever thread claimed the destination first.
- `DestinationOccupiedError` replaces the untyped `ArchiveWriteError` at
the three collision sites, carrying `source`, `destination`, `kind`
("convert" / "rename" / "inflight") and the rival `occupant`. Its
message texts are unchanged, and `__reduce__` keeps it picklable --
`WriteResult.error` is a public field and Python rebuilds an unpickled
exception as `cls(*self.args)`.
- `Comicbox.get_write_destination()` is now the one definition of where a
write lands. It was inlined twice, once from the sniffed archive type
and once from the suffix.
- `dump()` checks the destination before loading any metadata, so the CLI
benefits as well as the API.
- New `comicbox.predict.predict_write_destination()` reports the
destination without opening the archive.
- `bulk_write(preflight=True)` sniffs every destination on the first
`next()`, serially and in submission order, so an in-batch collision
loses deterministically. `preflight=False` restores the old path.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Sep 21, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
comicbox 5.2.0, PR X1 of 2. Implements §3–§5 and §8.1–§9 of
tasks/comicbox-5.2.0-plan.md. Mintsv5.2.0; X2 (mokkari 4.8.0 +cover_url_full) appends to the same NEWS section.The problem
A write's destination collision was only ever found after the work.
_create_zipfilenoticed the CBZ it would land on already existed — the common case,
because
delete_origdefaults off, so every re-tag of a kept originalhit it.
.cbz(Foo.cbtandFoo.cb7, one issue carried twice) were decided by whichever threadclaimed the destination first. The same batch could report either file
as the failure.
What changed
DestinationOccupiedError(comicbox.exceptions, re-exported fromcomicbox.write) replaces the untypedArchiveWriteErrorat the threecollision sites. It carries
source,destination,kind(
"convert"/"rename"/"inflight") and the rivaloccupant. Themessage texts are byte-identical to today's, so text matchers keep
working;
__reduce__keeps it picklable, becauseWriteResult.errorisa public field and Python rebuilds an unpickled exception as
cls(*self.args).Comicbox.get_write_destination()is now the single definition ofwhere a write lands. The rule was inlined twice — once from the sniffed
archive type in
write_archive_metadata, once from the suffix in_get_new_archive_path. Those disagree for a mis-suffixed archive.dump()refuses before_get_dump_formats(), so a kept-original CBRwhose CBZ twin exists costs no metadata parse at all — on the CLI path as
well as the API.
comicbox.predict.predict_write_destination()(new public module)reports the destination without opening the archive: construction sniffs
leading bytes and stops,
_archivestaysNone.bulk_write(preflight=True)sniffs every destination on the firstnext()— not at call time, so a 20k-file batch never blocks the call —serially and in submission order. The serial pass is deliberate and
commented: the claim map's "first submission wins" has to be
deterministic, and that determinism is the improvement over the
_INFLIGHT_DESTINATIONSthread race.preflight=Falserestores the oldbehavior.
_claim_destination/_INFLIGHT_DESTINATIONSstay untouched as thebackstop for concurrent callers — two
bulk_writecalls in oneprocess see each other only through it.
Behavior change
preflight=Trueas a default is a behavior change in a minor release: acaller that deliberately pre-created a destination is now refused up
front. Intended, and NEWS says so.
Verification
make lint→ 0 errors, 0 warnings, 0 notesmake ty→ All checks passed!make complexity→ no functions over 15; radoncc --min Candmi --min Bboth silentPlus the plan's §10 hand-off check by hand — the second run refuses,
names the CBZ, and logs neither
CreatingnorWrote metadata to::CI reports
skippingon PRs intodevelopby design; the local sequenceabove is the verification.
Deviation from the plan
The plan put the new bulk-write tests in
tests/unit/test_write_api.py.Appending them there dropped that file's radon maintainability index from
A to B, which
make complexityreports, so they live in their owntests/unit/test_write_destination_safety.pyinstead. Same coverage,gate stays clean.
Public surface codex Wave 2 imports
comicbox.predict.WriteDestination(path,destination,converts,occupied),comicbox.predict.predict_write_destination(path, *, base_config=None),comicbox.write.DestinationOccupiedError(also incomicbox.exceptions) with.source/.destination/.kind/.occupant,comicbox.write.bulk_write(..., preflight=True).🤖 Generated with Claude Code