Skip to content

v0.2.6a: CommandManager Paper Brigadier compatibility and permission scope fix

Latest

Choose a tag to compare

@castledking castledking released this 07 Jul 00:43

Allium v0.2.6a

Wiki: https://github.com/castledking/Allium/wiki

Highlights

  • Paper Brigadier compatibilityCommandManager now correctly handles commands registered via Paper's LifecycleEvents.COMMANDS (e.g., AbyssalLib's CommandBus, custom Brigadier dispatcher commands). These commands appear as VanillaCommandWrapper in the command map, whose testPermissionSilent() can false-negative. Allium now allows these commands through and defers permission evaluation to the server's native Brigadier dispatch.
  • Derived permission scoped to Allium-owning plugins only — The legacy heuristic that produced synthetic permissions like <pluginName>.<commandName> is now only applied to commands owned by Allium itself. For all other plugins — whether PluginCommand, VanillaCommandWrapper, or custom wrappers — Allium trusts the command's native permission system.

Technical Details

Problem

Commands registered through Paper's modern LifecycleEvents.COMMANDS API (used by AbyssalLib's CommandBus, among others) do not go through Bukkit's plugin.yml command registration. Instead, they register LiteralArgumentBuilder<CommandSourceStack> nodes directly into Mojang's Brigadier CommandDispatcher.

Paper wraps these dispatcher-only nodes in VanillaCommandWrapper (org.bukkit.craftbukkit.command.VanillaCommandWrapper) so they appear in SimpleCommandMap.knownCommands for Bukkit API compatibility. However, VanillaCommandWrapper.testPermissionSilent() does not reliably evaluate the underlying Brigadier node's requires() predicates, often returning false even when the player holds the correct permission.

This caused Allium's CommandManager to block all such commands and display the Allium "no-permission" message, even when the command's native permission system would have allowed execution.

Attempted Fixes (Rejected)

  1. Class name matching (className.contains("Brigadier") || className.contains("Wrapper")) — fragile, case-sensitive, and misses VanillaCommandWrapper in the general case since the wrapper extends Command/PluginCommand via different paths depending on the Paper version.
  2. PluginIdentifiableCommand instanceof check — Paper's MinecraftCommandMap$CommandWrapper may implement PluginIdentifiableCommand with a null plugin, making the check useless.
  3. PluginCommand instanceof check — some Paper wrapper variants extend PluginCommand directly, so negating PluginCommand doesn't reliably capture dispatcher-only commands.

Actual Fix

The fix uses a class-name-exact check for VanillaCommandWrapper as the first gate in hasPermissionForCommand():

if (className.contains("VanillaCommandWrapper")) {
    return true;
}

This immediately allows all Brigadier-dispatcher-registered commands and defers permission evaluation to the server's native dispatch (which correctly evaluates the CommandNode.canUse() predicates at execution time).

For PluginCommand instances, the revised logic is:

  1. Explicit permission setcommand.getPermission() is non-null → testPermissionSilent already checked it, allow.
  2. Derived permission — only computed for commands whose PluginIdentifiableCommand.getPlugin() returns "Allium". The derived string is allium.<baseCommand>.
  3. All other commands (PluginCommand from other plugins, vanilla commands with matching Bukkit wrappers) — trust testPermissionSilent.

getPluginForCommand Fallback Fix

The fallback in getPluginForCommand previously returned commandName.toLowerCase() when no plugin could be identified, causing the derived permission to be <commandName>.<commandName> (e.g., relique.relique). This is now null, which propagates through getDerivedPluginCommandPermission as null — treated as "no permission required".

Support and Feedback

Issues: https://github.com/castledking/Allium/issues
Wiki: https://github.com/castledking/Allium/wiki
Discord: https://discord.com/invite/pCKdCX6nYr
Website: https://castled.codes/