Skip to content

reenterConfiguration can send block_changed_ack in the configuration protocol and crash the server #14119

Description

@twisti-dev

Stack trace

Crash-Report: https://mclo.gs/MYzXc1J
Log: https://mclo.gs/e55gsTg

Plugin and Datapack List

[21:52:31] [Server thread/INFO]: ℹ Server Plugins (1):
[21:52:31] [Server thread/INFO]: Paper Plugins:
[21:52:31] [Server thread/INFO]:  - ReconfigurationReproducer
datapack list
[21:54:12 INFO]: There are 3 data pack(s) enabled: [vanilla (built-in)], [file/bukkit (world)], [paper (built-in)]
[21:54:12 INFO]: There are no more data packs available

Actions to reproduce (if known)

  1. Start a fresh server using the latest Paper 26.2 build.
  2. Install a minimal reproduction plugin containing the listener shown below.
  3. Join the server and break blocks.
  4. The plugin directly calls Player#getConnection().reenterConfiguration() from BlockBreakEvent.
  5. When PlayerConnectionReconfigureEvent is fired, the plugin immediately calls completeReconfiguration().
  6. Paper attempts to send clientbound/minecraft:block_changed_ack after the outbound connection has entered the configuration protocol.
  7. The player is disconnected, followed by IllegalStateException: Already retired. This exception can terminate the server.
  8. Repeat the block break if the timing is not hit on the first attempt.

Minimal reproduction code:

package dev.slne.surf.testplugin;

import io.papermc.paper.event.connection.configuration.PlayerConnectionReconfigureEvent;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.plugin.java.JavaPlugin;

public final class ReconfigurationReproducer extends JavaPlugin implements Listener {

    @Override
    public void onEnable() {
        getServer().getPluginManager().registerEvents(this, this);
    }

    @EventHandler
    public void onBlockBreak(final BlockBreakEvent event) {
        event.getPlayer().getConnection().reenterConfiguration();
    }

    @EventHandler
    public void onReconfigure(final PlayerConnectionReconfigureEvent event) {
        event.getConnection().completeReconfiguration();
    }
}

Paper version

[21:52:29] [Server thread/INFO]: Checking version, please wait...
[21:52:29] [ForkJoinPool.commonPool-worker-57/INFO]: This server is running Paper version 26.2-84-main@26e81c4 (2026-07-26T18:11:33Z) (Implementing API version 26.2.build.84-stable)
You are running the latest version
Previous version: 26.2-d9f1128 (MC: 26.2)

Other

Expected behavior:

Calling reenterConfiguration() should safely transition the player into the configuration phase, defer the transition until pending PLAY packets have been handled, or reject the call without corrupting the connection state.

It should not encode a PLAY packet using the CONFIGURATION protocol, disconnect the player, or crash the server.

Observed behavior:

The first relevant error is:

Error sending packet clientbound/minecraft:block_changed_ack
io.netty.handler.codec.EncoderException: Sending unknown packet 'clientbound/minecraft:block_changed_ack'

The player is then disconnected. During disconnect cleanup, Paper attempts to remove the player again even though the player was already removed while switching to the configuration phase. This produces:

java.lang.IllegalStateException: Already retired
    at io.papermc.paper.threadedregions.EntityScheduler.retire(...)
    at net.minecraft.world.entity.Entity.retireScheduler(...)
    at net.minecraft.server.players.PlayerList.remove(...)

The minimal reproduction invokes reenterConfiguration() directly from BlockBreakEvent because this makes the timing easy to reproduce.

However, the issue does not appear to be inherently specific to BlockBreakEvent. It can also occur when reenterConfiguration() is invoked from another task or event while the player happens to be breaking or interacting with a block and a block change acknowledgement is pending at the time of the protocol transition.

Calling the method after a fixed delay reduces the likelihood, but does not eliminate the problem if the player continues interacting with blocks during that delay.

Activity

added theissue type on Jul 28, 2026

electronicboy commented on Jul 28, 2026

@electronicboy
Member

entering reconfiguration when the server is processing a players packet and primed to send them data is going to cause issues, I don't think it's really on our plate to try to mitigate that, if you want to enter configuration you'll need to do it at a sane time.

There is no pending play packets, those packets are sent right after the event is fired as part of the our and mojangs standard interaction logic, neither we nor mojang expect you to enter reconfiguration there

Keviro commented on Jul 28, 2026

@Keviro

What do you define as a "sane time"? Theoretically a player could always be breaking a blocking when calling this API. I mean its minecraft overall

Or is it expected to basically freeze all player interactions before using this?

twisti-dev commented on Jul 28, 2026

@twisti-dev
Author

Thanks for the clarification. I understand that the minimal reproducer calls reenterConfiguration() re-entrantly while the server is still processing the ServerboundPlayerActionPacket, and that the block change acknowledgement is created after BlockBreakEvent returns.

However, I think there are still two separate concerns here.

First, the public API does not document this precondition. The Javadocs for PlayerGameConnection#reenterConfiguration() only state that it moves the player into the configuration stage:

https://github.com/PaperMC/Paper/blob/main/paper-api/src/main/java/io/papermc/paper/connection/PlayerGameConnection.java#L7-L13

There is no documented definition of a "sane time", nor does the public API provide a way to determine whether the connection is currently processing an interaction or is about to send a PLAY packet.

Moving the call into a delayed scheduler task only makes the issue less likely. In my original testing, it could still happen when the player happened to interact with or break another block shortly before the delayed task entered configuration. A plugin cannot atomically ensure that no new player action is processed between choosing to enter configuration and the actual protocol transition.

What is the supported way for a plugin to guarantee a safe transition? For example, is running it one tick later considered safe? If so, how should the plugin handle the player sending another block interaction before that task executes?

Second, even if calling the method from BlockBreakEvent is considered invalid usage, I do not think an invalid invocation of this public API should be able to crash the entire server. Paper could reject the call with an exception, defer the transition, or disconnect the affected player. Instead, after the encoder failure, disconnect cleanup removes the player a second time and EntityScheduler#retire() throws IllegalStateException: Already retired from the main server tick.

electronicboy commented on Jul 28, 2026

@electronicboy
Member

The API around some of this stuff is still new and with the expectation that you understand the underlying concepts of what is going on, that part is somewhat unavoidable around this sort of low-level API, any gating here would likely be fairly obtuse in terms of scope and I'm not sure how we'd we'd want that to cover

it not working safely from the scheduler sounds like a flaw in mojangs behavior here, paper is mostly just deferring to the vanilla logic here, I would presume that they'd be handling packets already on the wire with their entire configuration handshake

invoking API generally shouldn't cause crashes but with API at this low level of interaction, it gets a bit harsh we're exposing the ability to put clients into states which aren't exactly battle-tested in Mojang land

twisti-dev commented on Jul 28, 2026

@twisti-dev
Author

But then you can never actually use this API because it could crash the server at any time, right? That can't be right. Even if it's low-level.

electronicboy commented on Jul 28, 2026

@electronicboy
Member

That would look to be the case, mojang doesn't even use this mechanism outside of a debug command so they'd never have themselves in such a case of having player interactions around stuff occurring, I guess you'd probably need to litter a dozen "are they still in the world" checks around the incoming side, last I knew those checks weren't as strong as they should be for various reasons

twisti-dev commented on Jul 28, 2026

@twisti-dev
Author

Okay, then I guess we can't use the API, unfortunately. It looks like Folia has at least fixed the server crash (0001-Region-Threading-Base.patch, I think). In any case, the server doesn't crash with Folia—the player just gets disconnected. Maybe there could at least be a fix for the server crash.

Here is a log from Folia where the server didn't crash: https://mclo.gs/Aj1bN1S

YnsanitY16 commented on Oct 5, 2026

@YnsanitY16

I thinl this can be closed @twisti-dev

added
status: acceptedDisputed bug is accepted as valid or Feature accepted as desired to be added.
and removed on Oct 5, 2026

Warriorrrr commented on Oct 5, 2026

@Warriorrrr
Member

Resolved by #14346

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    status: acceptedDisputed bug is accepted as valid or Feature accepted as desired to be added.version: 26.2Game version 26.2

    Type

    Fields

    MC Issue

    None yet

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions