2026-09-18: a peer purchase paid 10 sats, the seller redeemed them, and the
buyer got no file plus a "seller doesn't accept your Cashu mint" error.
Three defects lined up:
1. The seller checked file existence with stat() but only read the file
AFTER redeeming the payment. Filebrowser-owned 0640 files (uid 100999)
passed stat but failed fs::read for the archipelago service user.
serve_content now checks existence and readability BEFORE the payment
gate, so an unservable file costs the buyer nothing.
2. The HTTP handler mapped every serve_content error to a bare, unlogged
404. A server-side failure is now a logged 500. (A 404 also makes the
buyer's Auto transport re-send the request over Tor.)
3. That re-send carried the same single-use token, which the mint had
already spent, so the seller answered 402. Redemption is now
idempotent: a token that verified for an item keeps authorising that
item for 10 minutes (per token, per item; SHA-256 keyed, in-memory,
concurrent requests serialised, failures never cached).
Buyer side: reclaim_spent_ecash now reports whether the refund worked, and
the error text no longer claims "refunded" when it wasn't, or asserts the
seller rejects the mint when the cause is unknown.
Adds tests for replay, concurrency, failure-not-cached, cross-item, and
unreadable-file-before-payment.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
claim_and_redeem retried every redeem failure indefinitely, including a
terminal one: mint error 11001 "Token Already Spent" (a claim replayed by a
relay-watermark edge case, or already redeemed by an earlier run). On
archy-x250-pa3 this pinned pending_claims at 1 forever and hammered
mint.minibits.cash's swap endpoint every ~6s, with the UI permanently
showing "a payment arrived but couldn't be redeemed yet".
- mint_client: expose the NUT error-code-11001 message as
ALREADY_REDEEMED_MSG so callers can recognize it without duplicating the
string.
- minibits: drop (not retry) a redeem failure that matches
is_already_redeemed — the value was already swept, so retrying can never
succeed.
- fetch_relay_dms: query the primary relay.minibits.cash alone first,
falling back to the public relay.damus.io/nos.lol only if it's
unreachable, and page past a 200-DM backlog instead of silently
stranding older DMs behind an un-advanced watermark.
This fix already existed on feat/minibits-lnurl-receive (4e410d7, 489995c,
2026-09-09) but that branch was never merged into main, which has its own
independently-diverged minibits.rs — so the bug shipped again in
1.8.16-alpha. Ported directly onto main's current implementation this time.
Immediate unblock on archy-x250-pa3: cleared the one poisoned
pending_claims entry from wallet/minibits.json by hand (already-redeemed,
zero value at risk) and restarted archipelago.service; confirmed via
journalctl that polling is quiet again.
See docs/incident-2026-09-15-minibits-already-redeemed.md for the full
writeup.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>