PaperMC / PaperMC/Paper

`reenterConfiguration` can send `block_changed_ack` in the configuration protocol and crash the server

Open
#14,119 7 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

status: needs triage version: 26.2
Dominant language
Java
Stars
12.7k
Forks
3.5k
Avg merge
3d 13h
Merged PRs (30d)
11

Description

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.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reproducing the issue with the provided ReconfigurationReproducer plugin on Paper 26.2, focusing on reenterConfiguration(), completeReconfiguration(), and the BlockBreakEvent timing. Trace the configuration transition and handling of block_changed_ack around PlayerConnectionReconfigureEvent. Done means the transition no longer encodes a PLAY packet as CONFIGURATION, disconnects the player, or causes the Already retired crash.

Written by the indexing model from the issue text.

Assessment

Tech stack
java
Domain
networking
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.