As an option, part of the deliver_to_target function could be wrapped in a try/catch block — in that case, empty planes will still arrive and get destroyed, but at least it won’t crash anymore.
  local success, err = pcall(function()
    local name, quality_count = next(source_scheduled)
    local quality, count = next(quality_count)
    if name and quality and count then
      count = math.min(count, get_stack_size(name))
      local target_scheduled = self.delivery_target.scheduled
      local removed = self.inventory.remove({name = name, quality = quality, count = count})
      if removed > 0 then
        self.delivery_target.inventory.insert({name = name, quality = quality, count = removed})
      end
      source_scheduled[name][quality] = source_scheduled[name][quality] - count
      if source_scheduled[name][quality] <= 0 then
        source_scheduled[name][quality] = nil
      end
      if not next(source_scheduled[name]) then
        source_scheduled[name] = nil
      end
      if target_scheduled[name] and target_scheduled[name][quality] then
        target_scheduled[name][quality] = target_scheduled[name][quality] - count
        if target_scheduled[name][quality] <= 0 then
          target_scheduled[name][quality] = nil
        end
        if not next(target_scheduled[name]) then
          target_scheduled[name] = nil
        end
      end
      delivery_time = self:make_delivery_particle(name)
    end
  end)