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

  1. 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
  2. Check the plugin for double-use of a resource handle after it has been dropped/closed
  3. Rebuild the plugin against the current plugin API version to match host resource-table behavior
  4. 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

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


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>> {
    state

View on GitHub (pinned to 8d4639e25a)