grammyjs/conversations

Error: Unexpected operation performed during replay (expected 'api' but was 'wait')! It looks like the conversation builder function is non-deterministic, or it relies on external data sources.

Karabur opened this issue · 2 comments

Conversation fails when used inside a menu:

steps to reproduce:

  • use following code
  • type any message to bot to get menu
  • click on a button
  • type anything when prompted
  • conversation fails on 'wait()' call
import { Conversation, ConversationFlavor, conversations, createConversation } from '@grammyjs/conversations'
import { Menu } from '@grammyjs/menu'
import { Bot, Context, session, SessionFlavor } from 'grammy'

type BotContext = Context & ConversationFlavor & SessionFlavor<SessionData>
type BotConversation = Conversation<BotContext>

interface SessionData {}

const bot = new Bot<BotContext>('token')
const menu = new Menu<BotContext>('menu')

async function startBot() {
  bot.use(
    session({
      initial: (): SessionData => ({}),
    })
  )
  bot.use(conversations())

  bot.use(createConversation(testConversation, 'test'))
  menu.text('Click me', (ctx) => ctx.conversation.enter('test'))

  bot.use(menu)

  bot.on('message', (ctx) =>
    ctx.reply('Here is a menu', {
      reply_markup: menu,
    })
  )

  bot.catch((err) => console.error(err))

  bot.start({ timeout: 5 })
}

async function testConversation(conversation: BotConversation, ctx: BotContext) {
  await ctx.reply('Type anything')
  ctx = await conversation.wait()
  await ctx.reply(`got ${ctx.message?.text}`)
}

startBot()

I can reproduce the problem. It is caused because the menu answers the callback query asynchronously in the background, so it won't block the middleware execution. This is done for performance reasons. However, concurrent middleware execution is by nature non-deterministic. The current version of the conversations plugin is not smart enough to handle that. We have already discussed in the group chat (https://t.me/grammyjs/52658) how to support this use case. It is not an easy problem, but I think we found a good new abstraction for the internal data structures that will enable this plugin to track conversation builder functions with a certain degree of non-determinism. This will be implemented before a stable version is released.

For now, you can disable autoAnswer in the menu plugin.

const menu = new Menu<BotContext>("menu", { autoAnswer: false });

This will prevent the plugin from performing the concurrent call. Note that you will now have to call ctx.answerCallbackQuery() manually. This is not ideal, but I do not think there is another workaround at the time which could be used until the conversations plugin fixes it.

I will leave this issue open until the plugin migrated to the new internal data structures.

I have a suspicion that #2 could be fixed the same way, but there is not enough information to reproduce it, so it could also be an unrelated problem.

Can you confirm that #15 fixes the problem?