Lua item:remove() before queued npc:teleport() hangs main thread

Issue Description

OpenMW's main thread will freeze when a Lua script calls item:remove() on an equipped item of a freshly-created NPC, then queues a :teleport() on that NPC.

Steps to Reproduce

repro.omwscripts

GLOBAL: scripts/repro/global.lua
PLAYER: scripts/repro/player.lua

scripts/repro/global.lua

local world = require('openmw.world')
local types = require('openmw.types')

local function reproduce()
    local sourceRecordId
    for _, actor in ipairs(world.activeActors) do
        if actor.type == types.NPC then
            sourceRecordId = actor.recordId
            break
        end
    end
    if not sourceRecordId then
        print('[REPRO] no NPC found in world.activeActors; move closer to one and retry')
        return
    end

    local player = world.players[1]
    if not player then
        print('[REPRO] no player')
        return
    end

    print(string.format('[REPRO] spawning fresh %s in draft cell', sourceRecordId))
    local npc = world.createObject(sourceRecordId)
    local equip = types.Actor.getEquipment(npc)
    local equippedItem
    if equip then
        for _, item in pairs(equip) do
            if item then
                equippedItem = item
                break
            end
        end
    end
    if not equippedItem then
        print(string.format(
            '[REPRO] %s spawned with no equipped items; bug needs an equipped slot. '
            .. 'Try a different NPC record (a guard works well).', sourceRecordId))
        return
    end

    print('[REPRO] queueing teleport (delayed action #1)')
    npc:teleport(player.cell, player.position)

    print(string.format('[REPRO] zeroing equipped item %s (synchronous setCount(0)); '
        .. 'queueing real removal as delayed action #2',
        tostring(equippedItem.recordId)))
    equippedItem:remove()

    print('[REPRO] freeze imminent on next applyDelayedActions')
end

return {
    eventHandlers = {
        ReproduceFreeze = reproduce,
    },
}

scripts/repo/player.lua

local core = require('openmw.core')

return {
    engineHandlers = {
        onKeyPress = function(key)
            if key.symbol == 'k' then
                core.sendGlobalEvent('ReproduceFreeze')
            end
        end,
    },
}

I was able to consistently reproduce the hang by doing the following:

  1. Start OpenMW Launcher
  2. Add repro.omwscripts to the data files
  3. Use Settings -> Testing -> 'Skip Menu and Generate Default Character' option
  4. After loading, open the in-game console (or put in launch script)
  5. coc "seyda neen"
  6. Press 'K' (trigger from the player script)
  7. Observe hang

System Information

  • OpenMW Version: OpenMW 0.51 RC 1
  • Operating System & Version: Windows 11 25H2
  • GPU: NVIDIA GeForce RTX 3080 Ti
  • Morrowind Version: Steam
  • Language: English
  • Addons: GOTY addons

Mods

  • Are you using mods? Yes
  • Are you using any shader mods? No
  • Does it happen in a clean vanilla install? No

Logs and Settings

If any are needed let me know and I'll add them.