{"record":{"id":"49452ad1adf73357","repo":"Pumpkin-MC/Pumpkin","slug":"invalid-text-component-resource-handle","errorCode":null,"errorMessage":"invalid text-component resource handle","messagePattern":"invalid text-component resource handle","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/player.rs","lineNumber":579,"sourceCode":"pub fn player_from_resource(\n    state: &PluginHostState,\n    player: &Resource<Player>,\n) -> wasmtime::Result<std::sync::Arc<crate::entity::player::Player>> {\n    state\n        .resource_table\n        .get::<PlayerResource>(&Resource::new_own(player.rep()))\n        .map_err(|_| wasmtime::Error::msg(\"invalid player resource handle\"))\n        .map(|resource| resource.provider.clone())\n}\n\npub(crate) fn text_component_from_resource(\n    state: &PluginHostState,\n    text: &Resource<pumpkin::plugin::text::TextComponent>,\n) -> pumpkin_util::text::TextComponent {\n    state\n        .resource_table\n        .get::<TextComponentResource>(&Resource::new_own(text.rep()))\n        .expect(\"invalid text-component resource handle\")\n        .provider\n        .clone()\n}\n\nfn world_from_resource(\n    state: &PluginHostState,\n    world: &Resource<pumpkin::plugin::world::World>,\n) -> std::sync::Arc<crate::world::World> {\n    state\n        .resource_table\n        .get::<WorldResource>(&Resource::new_own(world.rep()))\n        .expect(\"invalid world resource handle\")\n        .provider\n        .clone()\n}\n\nfn plugin_from_state(state: &PluginHostState) -> wasmtime::Result<Arc<WasmPlugin>> {\n    state","sourceCodeStart":561,"sourceCodeEnd":597,"githubUrl":"https://github.com/Pumpkin-MC/Pumpkin/blob/8d4639e25a57c15e47448ec327c780d41bbf2356/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/player.rs#L561-L597","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\n    state.resource_table\n        .get::<TextComponentResource>(&Resource::new_own(text.rep()))\n        .expect(\"invalid text-component resource handle\")\n        .provider.clone()\n// after\n    state.resource_table\n        .get::<TextComponentResource>(&Resource::new_own(text.rep()))\n        .map(|r| r.provider.clone())\n        .map_err(|_| wit::Error::invalid_handle(\"text-component\"))?","handlingStrategy":"validation","validationCode":"// plugin side: keep the handle alive until after the dialog call\nlet text = host.create_text_component(\"Hello\");\nhost.send_dialog(player, DialogInput::Text(\"q\".into(), text.clone()));\n// do not drop/close `text` before send_dialog returns","typeGuard":"fn has_text_component(state: &PluginHostState, rep: u32) -> bool {\n    state.resource_table\n        .get::<TextComponentResource>(&Resource::new_own(rep))\n        .is_ok()\n}","tryCatchPattern":null,"preventionTips":["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"],"tags":["wasm","plugin-api","panic","resource-handle"],"backgroundTag":"resource-not-found","analyzedSha":"8d4639e25a57c15e47448ec327c780d41bbf2356","analyzedAt":"2026-09-09T15:32:22.916Z","contentChangedAt":"2026-09-09T15:32:22.916Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}