fix(send): poll z_getoperationstatus without the per-opid filter

The opid poll called z_getoperationstatus(["opid"]) to check a specific
operation, but this daemon rejects the filtered form with "JSON value is not a
number/array as expected" (a UniValue error returned as an RPC error). The
poll's catch swallowed it, so every completed send stayed stuck on "Waiting
for operation" forever — confirmed via a Windows debug-log capture showing the
throw on every 2s cycle. The no-arg form works (verified in the console).

Call z_getoperationstatus with no arguments (returns ALL operations) and filter
to the opids we're tracking in parseOperationStatusPoll(). The parser now skips
any operation whose id isn't in the requested set, so unrelated/old operations
can't fire a spurious error toast or pollute send state. The stale-opid logic
is unchanged (the no-arg form still reports in-progress ops, so a genuinely
pending opid is never misread as stale).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-12 11:31:50 -05:00
parent 9ee8f9a43b
commit bc788d008e
2 changed files with 11 additions and 3 deletions

View File

@@ -774,12 +774,14 @@ void App::update()
opidWorker->post([this, opids]() -> rpc::RPCWorker::MainCb {
auto* rpc = (fast_rpc_ && fast_rpc_->isConnected()) ? fast_rpc_.get() : rpc_.get();
if (!rpc) return [this](){ opid_poll_in_progress_ = false; };
json ids = json::array();
for (const auto& id : opids) ids.push_back(id);
json result;
try {
rpc::RPCClient::TraceScope trace("Send tab / Operation status");
result = rpc->call("z_getoperationstatus", {ids});
// No per-opid filter: this daemon rejects z_getoperationstatus(["opid"]) with
// "JSON value is not an array as expected", which left completed sends stuck on
// "Waiting for operation". The no-arg form returns ALL operations;
// parseOperationStatusPoll() filters down to the opids we're tracking.
result = rpc->call("z_getoperationstatus", json::array());
} catch (...) {
return [this](){ opid_poll_in_progress_ = false; };
}

View File

@@ -1072,11 +1072,17 @@ NetworkRefreshService::OperationStatusPollResult NetworkRefreshService::parseOpe
OperationStatusPollResult parsed;
if (!result.is_array()) return parsed;
// We poll z_getoperationstatus with no filter (this daemon rejects the per-opid filtered form),
// so the result lists ALL operations. Only act on the ones we're tracking — otherwise an
// unrelated failed/old operation would fire a spurious error toast or pollute send state.
const std::set<std::string> requested(requestedOpids.begin(), requestedOpids.end());
std::set<std::string> reported;
for (const auto& op : result) {
if (!op.is_object()) continue;
std::string opid = op.value("id", std::string());
if (opid.empty()) continue;
if (requested.find(opid) == requested.end()) continue; // not one of ours — ignore
reported.insert(opid);
std::string status = op.value("status", std::string());