From 2b2a14c569a69294dd65af89883e88e06176c492 Mon Sep 17 00:00:00 2001 From: ssmithx Date: Mon, 7 Sep 2026 17:32:26 +0000 Subject: [PATCH] docs(tollgate-sweep): document two live-confirmed drain-CLI bugs sweep_once() has never actually swept anything: `tollgate wallet drain cashu` (no flags) blocks on an interactive y/N confirmation that Router::run can never answer over a non-PTY SSH exec (empty stdin -> EOF -> defaults to N -> "Operation cancelled." with exit code 0), so the drain_code != 0 check can't catch it and every tick silently no-ops. The obvious fix isn't safe either: `--json` skips the prompt, but confirmed live against archy-x250-pa3 that on a wallet.db with a stale duplicate per-mint entry (trailing-slash leftover from before the mint_url fix), it completes a real swap against the good entry, then aborts on the second (empty, stale) entry and reports "success": false without ever printing or persisting the resulting token anywhere. 50 sats went from spendable balance to gone in that one call. Documented so nobody "fixes" this by wiring in --json before upstream fixes the partial-failure data loss. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01KTdMfVJChCwCCYF1ZTRQLc --- core/archipelago/src/tollgate_sweep.rs | 40 ++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/core/archipelago/src/tollgate_sweep.rs b/core/archipelago/src/tollgate_sweep.rs index b86a7be9..4fbf561f 100644 --- a/core/archipelago/src/tollgate_sweep.rs +++ b/core/archipelago/src/tollgate_sweep.rs @@ -22,6 +22,46 @@ use crate::wallet::ecash; /// /// Returns the total sats swept in (0 if there was nothing to do, including /// when no router is configured or it doesn't have TollGate installed). +/// +/// # KNOWN BROKEN as of 2026-09-07 — do not "fix" by adding `--json` without +/// reading the rest of this comment first. +/// +/// Confirmed live against archy-x250-pa3, two stacked bugs in the upstream +/// `tollgate` CLI, not in this function: +/// +/// 1. **This call never actually drains anything.** `tollgate wallet drain +/// cashu` (no flags — what this function runs) prints an interactive +/// `Are you sure? (y/N)` confirmation and reads stdin for the answer. +/// `Router::run` executes over SSH with no PTY and empty stdin, so it +/// always reads EOF, defaults to "N", and prints "Operation cancelled." — +/// **with exit code 0**. The `drain_code != 0` check below can never catch +/// this, so every single tick silently falls through to "no `Token:` +/// lines found" → `Ok(0)`. No error, no log line (even at `warn!`), just +/// quiet total inaction, forever. This has presumably never swept a +/// single sat on any node. +/// +/// 2. **The obvious fix is worse.** `tollgate --json wallet drain cashu` +/// *does* skip the confirmation prompt — but confirmed live: when the +/// wallet's internal per-mint registry holds more than one entry for what +/// is really the same mint (here: `https://mint.minibits.cash/Bitcoin` vs. +/// a stale `.../Bitcoin/` — leftover from before the trailing-slash +/// `mint_url` fix elsewhere in this codebase; `wallet.db` still had a +/// proof/registry entry keyed under the old slashed URL even after +/// `config.json` was corrected), the CLI appears to complete a real swap +/// against the *good* entry — spending and irreversibly consuming the +/// original proofs, per how Cashu swaps work — then hits the second, +/// empty, stale-keyed entry, reports the whole command as +/// `"success": false`, and **never prints or persists the resulting +/// token anywhere** (checked every location its own "will be saved to a +/// file" warning implies: `/etc/tollgate/ecash/`, `/root`, `/tmp`, +/// nothing). Balance went from 50 sats to 0 across that one call. The +/// funds are gone — there is no undo once a swap is submitted to the +/// mint. +/// +/// Do not wire `--json` into this function until upstream fixes partial +/// per-mint failure handling in `drain cashu` to preserve/return whatever it +/// already successfully drained. Until then, the current silent-no-op +/// behavior, while useless, is at least safe. pub async fn sweep_once(data_dir: &Path) -> Result { let cfg = net_router::load_router_config(data_dir).await?; if !cfg.configured {