Anuken/Mindustry · error · IOException

Failed to send packet: {}

Error message

Failed to send packet: {}

What it means

Thrown inside sendClient after snet.sendMessageToConnection returns SteamResult.InvalidParam, NoConnection, or InvalidState. These three codes mean the connection is unusable (dropped, not yet established, or given bad arguments), so the packet cannot be delivered. The exception is then caught by the surrounding try and passed to net.showError, surfacing a network-failure dialog to the player.

Source

Thrown at desktop/src/mindustry/desktop/steam/SNet.java:223

    @Override
    public void sendClient(Object object, boolean reliable){
        if(isSteamClient()){
            if(currentServer == null || clientConnection == null){
                Log.info("Not connected, quitting.");
                return;
            }

            try{
                clientWriteBuffer.limit(clientWriteBuffer.capacity());
                clientWriteBuffer.position(0);
                serializer.write(clientWriteBuffer, object);
                int length = clientWriteBuffer.position();
                clientWriteBuffer.flip();

                var result = snet.sendMessageToConnection(clientConnection, clientWriteBuffer, reliable || length >= 1000 ? SendFlags.ReliableNoNagle : SendFlags.UnreliableNoDelay);

                if(result == SteamResult.InvalidParam || result == SteamResult.NoConnection || result == SteamResult.InvalidState){
                    throw new IOException("Failed to send packet: " + result);
                }
            }catch(Exception e){
                net.showError(e);
            }
        }else{
            provider.sendClient(object, reliable);
        }
    }

    @Override
    public void disconnectClient(){
        stopNetThread();

        if(isSteamClient()){
            if(currentLobby != null) smat.leaveLobby(currentLobby);
            if(clientConnection != null) snet.closeConnection(clientConnection, 0, false);
            clientConnection = null;
            currentServer = null;

View on GitHub (pinned to f695ad7e60)

Solutions

  1. Gate sends on a connected flag that is set only when onConnectionStatusChanged reports Connected and cleared on None/FailedByRemote.
  2. Treat NoConnection as recoverable: suppress the error dialog and trigger a reconnect instead of showing it.
  3. Drop or re-queue early-sends during the connecting window rather than letting them hit sendMessageToConnection.
  4. Log the SteamResult value so transient vs. fatal failures are distinguishable.

Example fix

// before
var result = snet.sendMessageToConnection(clientConnection, clientWriteBuffer, reliable || length >= 1000 ? SendFlags.ReliableNoNagle : SendFlags.UnreliableNoDelay);
if(result == SteamResult.InvalidParam || result == SteamResult.NoConnection || result == SteamResult.InvalidState){
    throw new IOException("Failed to send packet: " + result);
}

// after
var result = snet.sendMessageToConnection(clientConnection, clientWriteBuffer, reliable || length >= 1000 ? SendFlags.ReliableNoNagle : SendFlags.UnreliableNoDelay);
if(result == SteamResult.NoConnection || result == SteamResult.InvalidState){
    Log.warn("Send failed, connection lost (@); suppressing.", result);
    netClient.disconnectQuietly();
    return;
}
if(result == SteamResult.InvalidParam){
    throw new IOException("Failed to send packet: " + result);
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Track connection readiness; only send when Connected
if(!isSteamClient() || clientConnection == null || !connectionReady){
    Log.info("Not connected, skipping send.");
    return;
}

Type guard

static boolean canSendToSteam(Connection c, boolean ready){
    return c != null && ready;
}

Try / catch

try{
    serializer.write(clientWriteBuffer, object);
    var result = snet.sendMessageToConnection(clientConnection, clientWriteBuffer, flags);
    if(result == SteamResult.NoConnection || result == SteamResult.InvalidState){
        Log.warn("P2P send failed (@); reconnecting.", result);
        netClient.disconnectQuietly();
        return;
    }
    if(result == SteamResult.InvalidParam) throw new IOException("Failed to send packet: " + result);
}catch(Exception e){
    net.showError(e);
}

Prevention

When it happens

Trigger: Calling sendClient while clientConnection is closed/timed out (NoConnection), before connectP2P has reached the Connected state (InvalidState), or with a buffer/connection in an inconsistent state (InvalidParam). Common during the race between connection setup and queued sends, or after a silent disconnect.

Common situations: Sending packets immediately after connectP2P before onConnectionStatusChanged fires; sending after the peer dropped without local detection; a large reliable packet hitting a connection that the peer already closed; mobile network handoff severing the P2P link.

Related errors


AI-assisted analysis of Anuken/Mindustry@f695ad7e60 (2026-08-14). Data as JSON: /api/errors/31c232d7b721ebb0. Report an issue: GitHub.