The docs for `blocksonly` say that "Transactions from the wallet or RPC are not affected" but the code says:

    if (gArgs.GetBoolArg("-blocksonly", DEFAULT_BLOCKSONLY) && gArgs.SoftSetBoolArg("-walletbroadcast", false)) {
        LogPrintf("%s: parameter interaction: -blocksonly=1 -> setting -walletbroadcast=0\n", __func__);
    }

Here's the relevant documentation:

    -blocksonly
       Whether to reject transactions from network peers. Transactions from the
       wallet or RPC are not affected. (default: 0)

    -walletbroadcast
       Make the wallet broadcast transactions (default: 1)

Doesn't that imply that `blocksonly` *does* affect transactions from the wallet?

NetWhitelistPermissions subnet;
        std::string error;
        if (!NetWhitelistPermissions::TryParse(net, subnet, error)) return InitError(error);
        connOptions.vWhitelistedRange.push_back(subnet);
    }
    connOptions.vSeedNodes = gArgs.GetArgs("-seednode");
    
    connOptions.m_use_addrman_outgoing = !gArgs.IsArgSet("-connect");
    if (!connOptions.m_use_addrman_outgoing) {
        const auto connect = gArgs.GetArgs("-connect");
        if (connect.size() != 1 || connect[0] != "0") {
            connOptions.m_specified_outgoing = connect;
        }
    }
    if (!node.connman->Start(*node.scheduler, connOptions)) {
        return false;
    }
    
    SetRPCWarmupFinished();
    uiInterface.InitMessage(_("Done loading").translated);
    for (const auto& client : node.chain_clients) {
        client->start(*node.scheduler);
    }
    BanMan* banman = node.banman.get();
    node.scheduler->scheduleEvery([banman]{
        banman->DumpBanlist();
    }, DUMP_BANS_INTERVAL);
    return true;
}