Pumpkin-MC/Pumpkin · error
invalid text-component resource handle
Error message
invalid text-component resource handle
What it means
text_component_from_resource resolves a plugin-supplied Resource handle back to the TextComponent stored in the plugin host's resource table, and expects the lookup to succeed. The expect fires when the handle is invalid: it was never inserted, was already consumed/freed, or belongs to a different plugin host state. It protects the host from dereferencing dangling or forged resource handles passed across the WIT boundary.
Solutions
- Ensure the TextComponent resource is still alive (not cleaned up) at the time the dialog API is called and create a fresh handle per call
- Check the plugin for double-use of a resource handle after it has been dropped/closed
- Rebuild the plugin against the current plugin API version to match host resource-table behavior
- Host-side: return a WIT error instead of panicking so plugins get an actionable failure
Example fix
// before
state.resource_table
.get::<TextComponentResource>(&Resource::new_own(text.rep()))
.expect("invalid text-component resource handle")
.provider.clone()
// after
state.resource_table
.get::<TextComponentResource>(&Resource::new_own(text.rep()))
.map(|r| r.provider.clone())
.map_err(|_| wit::Error::invalid_handle("text-component"))? Defensive patterns
Strategy: validation
Validate before calling
// plugin side: keep the handle alive until after the dialog call
let text = host.create_text_component("Hello");
host.send_dialog(player, DialogInput::Text("q".into(), text.clone()));
// do not drop/close `text` before send_dialog returns Type guard
fn has_text_component(state: &PluginHostState, rep: u32) -> bool {
state.resource_table
.get::<TextComponentResource>(&Resource::new_own(rep))
.is_ok()
} Prevention
- Never reuse a resource handle after the resource was dropped or the host restarted
- Create a fresh TextComponent handle per dialog API call
- Track resource lifetimes in the plugin; close handles exactly once
- Rebuild plugins when upgrading the server's plugin API
When it happens
Trigger: A plugin passes a stale or already-closed TextComponent resource handle to dialog-related APIs — ProtocolDialogBody::PlainMessage, ProtocolDialogInput::Boolean/Text/NumberRange/SingleOption, or protocol_dialog_from_wasm; passing a handle created in a different PluginHostState; double-cleanup of the resource.
Common situations: A plugin keeps a TextComponent handle across plugin reload/host restart; a plugin bug closes the resource and reuses it; API version changes that alter resource lifetime management between plugin and host.
Understand the failure class
Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.
Related errors
- failed to add player resource
- failed to add player resource
- unexpected event type
- valid enchantment ID
- Cannot construct BlockFertilizeEvent from WASM
AI-assisted analysis of Pumpkin-MC/Pumpkin@8d4639e25a (2026-09-09).
Data as JSON: /api/errors/49452ad1adf73357.
Report an issue: GitHub.
Appendix: source
Thrown at crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/player.rs:579
pub fn player_from_resource(
state: &PluginHostState,
player: &Resource<Player>,
) -> wasmtime::Result<std::sync::Arc<crate::entity::player::Player>> {
state
.resource_table
.get::<PlayerResource>(&Resource::new_own(player.rep()))
.map_err(|_| wasmtime::Error::msg("invalid player resource handle"))
.map(|resource| resource.provider.clone())
}
pub(crate) fn text_component_from_resource(
state: &PluginHostState,
text: &Resource<pumpkin::plugin::text::TextComponent>,
) -> pumpkin_util::text::TextComponent {
state
.resource_table
.get::<TextComponentResource>(&Resource::new_own(text.rep()))
.expect("invalid text-component resource handle")
.provider
.clone()
}
fn world_from_resource(
state: &PluginHostState,
world: &Resource<pumpkin::plugin::world::World>,
) -> std::sync::Arc<crate::world::World> {
state
.resource_table
.get::<WorldResource>(&Resource::new_own(world.rep()))
.expect("invalid world resource handle")
.provider
.clone()
}
fn plugin_from_state(state: &PluginHostState) -> wasmtime::Result<Arc<WasmPlugin>> {
stateView on GitHub (pinned to 8d4639e25a)