Description
When any earn-exp event triggers a level-up that has reward commands configured, CyberLevels calls Bukkit.dispatchCommand() directly from the event handler. On Folia (and its forks), dispatchCommand requires the global tick thread — calling it from a region thread throws IllegalStateException: Dispatching command async.
The moving event is the most visible trigger because it fires on every player movement, but the same crash will occur with any event (killing, breaking, fishing, etc.) if it causes a level-up while rewards contain commands.
Steps to Reproduce
- Run CyberLevels 1.2.5 on a Folia-based server (tested on CanvasMC 26.1.2)
- Enable any earn-exp event in
earn-exp.yml
- Add at least one
commands entry to rewards.yml
- Have a player trigger the event and reach a reward level
Stack Trace
[Folia Region Scheduler Thread #1/ERROR]: Could not pass event PlayerMoveEvent to CyberLevels v1.2.5
java.lang.IllegalStateException: Dispatching command async
at io.papermc.paper.threadedregions.RegionizedServer.ensureGlobalTickThread(RegionizedServer.java:149)
at org.bukkit.craftbukkit.CraftServer.dispatchCommand(CraftServer.java:964)
at org.bukkit.Bukkit.dispatchCommand(Bukkit.java:1132)
at com.bitaspire.cyberlevels.cache.Rewards$RewardImpl.executeCommands(Rewards.java:117)
at com.bitaspire.cyberlevels.level.Reward.giveAll(Reward.java:41)
at com.bitaspire.cyberlevels.BaseSystem$BaseUser.lambda$sendLevelReward$0(BaseSystem.java:479)
at com.bitaspire.cyberlevels.BaseSystem$BaseUser.sendLevelReward(BaseSystem.java:479)
at com.bitaspire.cyberlevels.BaseSystem$BaseUser.changeExp(BaseSystem.java:571)
at com.bitaspire.cyberlevels.BaseSystem$BaseUser.addExp(BaseSystem.java:693)
at com.bitaspire.cyberlevels.cache.EarnExp.sendPermissionExp(EarnExp.java:494)
at com.bitaspire.cyberlevels.cache.EarnExp$6.onMovement(EarnExp.java:228)
Environment
- CyberLevels: 1.2.5
- Server: CanvasMC 26.1.2-757 (Folia fork)
- Java: 21
Suggested Fix
In Rewards$RewardImpl.executeCommands, wrap the Bukkit.dispatchCommand() call in a Folia-safe scheduler. For Folia, the command must be dispatched on the global region thread:
// Instead of:
Bukkit.dispatchCommand(sender, command);
// Use:
if (Bukkit.getServer() instanceof io.papermc.paper.threadedregions.RegionizedServer) {
Bukkit.getGlobalRegionScheduler().run(plugin, task -> Bukkit.dispatchCommand(sender, command));
} else {
Bukkit.dispatchCommand(sender, command);
}
Description
When any earn-exp event triggers a level-up that has reward
commandsconfigured, CyberLevels callsBukkit.dispatchCommand()directly from the event handler. On Folia (and its forks),dispatchCommandrequires the global tick thread — calling it from a region thread throwsIllegalStateException: Dispatching command async.The
movingevent is the most visible trigger because it fires on every player movement, but the same crash will occur with any event (killing, breaking, fishing, etc.) if it causes a level-up while rewards contain commands.Steps to Reproduce
earn-exp.ymlcommandsentry torewards.ymlStack Trace
Environment
Suggested Fix
In
Rewards$RewardImpl.executeCommands, wrap theBukkit.dispatchCommand()call in a Folia-safe scheduler. For Folia, the command must be dispatched on the global region thread: