Skip to content

Number a colliding filename instead of failing the transfer - #35

Open
op-q wants to merge 2 commits into
mainfrom
fix/receiver-name-collision
Open

Number a colliding filename instead of failing the transfer#35
op-q wants to merge 2 commits into
mainfrom
fix/receiver-name-collision

Conversation

@op-q

@op-q op-q commented Aug 20, 2026

Copy link
Copy Markdown
Owner

Reported from a real transfer: receiving a file whose name was already taken killed the transfer on both sides.

$ drop recv 4607F9
Receiving Lucas_Lifbom_Resume-1.pdf (67.9 KiB)
error: ./Lucas_Lifbom_Resume-1.pdf already exists; pass --force to overwrite it …

$ drop send Lucas_Lifbom_Resume-1.pdf
Sending    0.0%  0 B / 67.9 KiB  0 B/s  ETA --error: receiver disconnected

Why it was worse than it looks

The receiver refused before a byte moved, returned an error, and dropped the socket. The relay read that as a disconnect and told the sender receiver disconnected — which blames the wrong side and gives no hint that a filename was the cause. The session was consumed either way, so the code could not be reused; both people had to start over.

Change

A single file whose name is taken is saved beside the original with a number added, so report.pdf arriving twice leaves report.pdf and report-1.pdf. The number goes before the extension so the file still opens with the right application.

The receiver's existing file is still never replaced without --force — that was the invariant worth protecting, and refusing the transfer was never required to uphold it.

The name is claimed with create_new rather than by testing exists() and then creating. That is what makes numbering correct under concurrency: the old check left a window where two receives could agree on the same free name and one would silently overwrite the other. Closing that was not the point of this change, but leaving it while adding a retry loop on top would have made it easier to hit.

Archive extraction is unchanged. A colliding entry inside a tar is still skipped and reported while the rest continues. Renaming individual files inside a tree the sender laid out would produce a directory matching neither what was sent nor what was already there.

Verification

Reproduced the original scenario against the live relay:

before: report.pdf
after:  report-1.pdf  report.pdf
receiver: report.pdf already exists; saving as report-1.pdf instead
sender:   Sent 20.0 KiB.

The sender now completes instead of reporting a disconnect.

Tests: 62 passing, up from 56 — five unit tests covering the naming rule (extension placement, no extension, leading dot, walking past taken names, never replacing an existing file) and one integration test that sends the same file twice into one destination and asserts both copies exist.

scripts/check-secrets.sh, cargo fmt --check, cargo clippy --workspace --all-targets --all-features -D warnings all pass.

Docs

README updated to describe the split behaviour (numbered for a single file, skipped for an archive entry). The receiver-confirmation plan is updated too: numbering makes the prompt-ordering constraint sharper, since a prompt placed after open_target would now leave an empty report-1.pdf behind on decline and consume that name.

Not fixed here

Two other papercuts from the same session, both worth their own change:

  • Lowercase codes are rejected. drop recv 4607f9 returns invalid session code; 4607F9 works. The relay stores codes uppercase and encode_code does not normalise case. A two-line client-side fix.
  • receiver disconnected is the sender's message for any receiver-side failure. This change removes the most common cause, but a receiver that fails for another reason still produces a misleading message. The receiver could send an error control frame before closing so the sender can say what actually happened.

🤖 Generated with Claude Code

Receiving a file whose name was already taken killed the whole transfer. The
receiver refused before a byte moved, returned an error, and dropped the
socket; the relay read that as a disconnect and told the sender "receiver
disconnected", which blames the wrong side and gives no hint that a filename
was the problem. The session was consumed either way, so the code could not
simply be reused.

A single file whose name is taken is now saved beside the original with a
number added: `report.pdf` arriving twice leaves `report.pdf` and
`report-1.pdf`. The receiver's existing file is still never replaced without
`--force`, which is the invariant that mattered; refusing the transfer was
never required to uphold it.

The name is claimed with `create_new` rather than by testing `exists()` and
then creating. That is what makes the numbering correct under concurrency: the
old check left a window in which two receives could agree on the same free name
and one would silently overwrite the other.

Archive extraction is unchanged. A colliding entry inside a tar is still
skipped and reported while the rest continues, because renaming individual
files inside a tree the sender laid out would produce a directory that matches
neither what was sent nor what was already there.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 20, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
drop Ready Ready Preview Aug 20, 2026 12:37pm

The behaviour change was documented in the README, but the reasoning and the
loose ends lived only in a pull request description — which is where the relay
teardown defect sat for a month before it was rediscovered.

Adds a decision entry for the collision policy, because the split between it
and archive extraction reads as an inconsistency and will invite someone to
"fix" one side into matching the other.

Adds three items to the unscheduled list: lowercase session codes being
rejected, "receiver disconnected" being the sender's message for every
receiver-side failure, and the published binary reporting 0.1.0 while the tag
says v0.1.1.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant