Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

processBeforeRepsonse: true doesn't work when a Bolt listener function has WebClient calls inside #462

Closed
3 tasks done
aoberoi opened this issue Apr 4, 2020 · 16 comments · Fixed by #469
Closed
3 tasks done
Assignees
Labels
bug M-T: confirmed bug report. Issues are confirmed when the reproduction steps are documented

Comments

@aoberoi
Copy link
Contributor

aoberoi commented Apr 4, 2020

Description

When deploying a Bolt app to AWS Lambda, and using processBeforeResponse option, the listener is not able to complete before the HTTP response is finished.

This issue was reported by a user in the Slack Community workspace: https://community.slack.com/archives/CHY642221/p1585670255001600.

Requirements (place an x in each of the [ ])

  • I've read and understood the Contributing guidelines and have done my best effort to follow them.
  • I've read and agree to the Code of Conduct.
  • I've searched for any related issues and avoided creating a duplicate issue.

Bug Report

Filling out the following details about bugs will help us solve your issue sooner.

Reproducible in:

package version: 2.0.0

node version: NA

OS version(s): NA

Steps to reproduce:

  1. Use the following code:

    const { App, ExpressReceiver } = require('@slack/bolt');
    const expressReceiver = new ExpressReceiver({
      signingSecret: process.env.SLACK_SIGNING_SECRET
    });
    const app = new App({
      token: process.env.SLACK_BOT_TOKEN,
      receiver: expressReceiver,
      processBeforeResponse: true
    });
    // ------------------------
    // Application Logic
    // ------------------------
    app.event('app_home_opened', async ({ context, event, say }) => {
      console.log(`event: ${JSON.stringify(event)}`);
      await say(`Hello world, and welcome <@${event.user}> from AWS Lambda.`);
      console.log("finish to say");
    });
    
  2. Deploy to AWS Lambda

  3. Open the App Home in Slack to trigger the event subscription.

Expected result:

User should receive the message sent using say(), and the logs should show "finish to say".

Actual result:

There's no message and the line does not appear in the logs.

@aoberoi aoberoi added the needs info An issue that is claimed to be a bug and hasn't been reproduced, or otherwise needs more info label Apr 4, 2020
@aoberoi
Copy link
Contributor Author

aoberoi commented Apr 4, 2020

#459 (by @seratch) contains one potential fix for this issue. this fix involves a timeout which is not ideal, so we are exploring other solutions still.

the priority of this problem is high so we might release with this PR to unblock users who need to deploy for FaaS platforms like Lambda.

@aoberoi
Copy link
Contributor Author

aoberoi commented Apr 4, 2020

I have tried reproducing this issue locally but the behavior works as expected so far.

First, I added a few log statements to src/ExpressReceiver.ts. here is the diff:

diff --git a/src/ExpressReceiver.ts b/src/ExpressReceiver.ts
index f125bfa..0ea0d3c 100644
--- a/src/ExpressReceiver.ts
+++ b/src/ExpressReceiver.ts
@@ -72,6 +72,7 @@ export default class ExpressReceiver implements Receiver {
     const event: ReceiverEvent = {
       body: req.body,
       ack: async (response): Promise<void> => {
+        this.logger.debug('ack() begin');
         if (isAcknowledged) {
           throw new ReceiverMultipleAckError();
         }
@@ -82,6 +83,7 @@ export default class ExpressReceiver implements Receiver {
           } else {
             storedResponse = response;
           }
+          this.logger.debug('ack() response stored');
         } else {
           if (!response) {
             res.send('');
@@ -90,18 +92,21 @@ export default class ExpressReceiver implements Receiver {
           } else {
             res.json(response);
           }
+          this.logger.debug('ack() response sent');
         }
       },
     };
 
     try {
       await this.bolt?.processEvent(event);
+      this.logger.debug('event processed');
       if (storedResponse !== undefined) {
         if (typeof storedResponse === 'string') {
           res.send(storedResponse);
         } else {
           res.json(storedResponse);
         }
+        this.logger.debug('stored response sent');
       }
     } catch (err) {
       res.send(500);

Next, I ran the following program twice. Once with processBeforeResponse set to false and once with it set to true.

const { App, LogLevel } = require('./dist');
const { request } = require('http');
const { createHmac } = require('crypto');

const processBeforeResponse = false;
const signingSecret = 'foobar';

const app = new App({
  signingSecret,
  processBeforeResponse,
  authorize() { return Promise.resolve({}); },
  ignoreSelf: false,
  logLevel: LogLevel.DEBUG,
});

app.event('app_mention', async ({ event }) => {
  console.log('event listener', processBeforeResponse, event);
  console.log('before fakeSay');
  await fakeSay();
  console.log('after fakeSay');
});

app.action('my_action_id', async ({ action, ack }) => {
  console.log('action listener', processBeforeResponse, action);
  console.log('before ack');
  await ack();
  console.log('after ack');

  console.log('before fakeSay');
  await fakeSay();
  console.log('after fakeSay');
});

(async () => {
  const port = 8080;
  await app.start(port);
  console.log('app started');

  const boltRequest = createBoltRequester(signingSecret, port);

  // send a fake Events API request
  const eventResponse = await boltRequest({
    event: {
      type: 'app_mention',
      foo: 'bar',
    },
  }, signingSecret);
  console.log('event HTTP response received', eventResponse);

  // send a fake block action request
  const actionResponse = await boltRequest({
    type: 'block_actions',
    actions: [{
      action_id: 'my_action_id',
      beep: 'boop',
    }],
    channel: {},
    user: {},
    team: {},
  }, signingSecret);
  console.log('action HTTP response received', actionResponse);
})().catch(console.error);


function createBoltRequester(signingSecret, port) {
  return function boltRequest(requestBody) {

    const serializedRequestBody = JSON.stringify(requestBody);
    const timestamp = (Date.now() / 1000).toFixed();
    const hmac = createHmac('sha256', signingSecret);
    hmac.update(`v0:${timestamp}:${serializedRequestBody}`);
    const signature = `v0=${hmac.digest('hex')}`;

    return new Promise((resolve, reject) => {
      const req = request({
        port,
        method: 'POST',
        path: '/slack/events',
        timeout: 10 * 1000, // 10 seconds
        headers: {
          'X-Slack-Request-Timestamp': timestamp,
          'X-Slack-Signature': signature,
          'Content-Type': 'application/json',
        },
      }, (res) => {
        if (res.statusCode !== 200) {
          reject(new Error(`Bolt request received a response with non-200 status code: ${res.statusCode}`));
          return;
        }
        let responseBody = '';
        res.setEncoding('utf8');
        res.on('data', (chunk) => responseBody += chunk);
        res.on('aborted', reject);
        res.on('end', () => {
          if (responseBody === '') {
            resolve({});
          } else {
            resolve(JSON.parse(responseBody))
          }
        });
      });

      req.on('error', reject);

      req.on('timeout', () => {
        req.abort();
      });

      req.write(serializedRequestBody);
      req.end();
    });
  };
}

function fakeSay() {
  return new Promise((resolve) => {
    // random delay between 1-6 seconds
    setTimeout(resolve, (Math.random() * 5000) + 1000);
  });
}

Here are the outputs:

processBeforeResponse=false:
[DEBUG]  WebClient:0 initialized
app started
[DEBUG]  bolt-app ack() begin
[DEBUG]  bolt-app ack() response sent
[DEBUG]  bolt-app No conversation ID for incoming event
event listener false { type: 'app_mention', foo: 'bar' }
before fakeSay
event HTTP response received {}
[DEBUG]  bolt-app No conversation ID for incoming event
action listener false { action_id: 'my_action_id', beep: 'boop' }
before ack
[DEBUG]  bolt-app ack() begin
[DEBUG]  bolt-app ack() response sent
after ack
before fakeSay
action HTTP response received {}

In this run, for the events API request, the ack() response sent log occurs before the event processed log as expected. And for the action request, the same is true.

processBeforeResponse=true:
[DEBUG]  WebClient:0 initialized
app started
[DEBUG]  bolt-app ack() begin
[DEBUG]  bolt-app ack() response stored
[DEBUG]  bolt-app No conversation ID for incoming event
event listener true { type: 'app_mention', foo: 'bar' }
before fakeSay
after fakeSay
[DEBUG]  bolt-app event processed
[DEBUG]  bolt-app stored response sent
event HTTP response received {}
[DEBUG]  bolt-app No conversation ID for incoming event
action listener true { action_id: 'my_action_id', beep: 'boop' }
before ack
[DEBUG]  bolt-app ack() begin
[DEBUG]  bolt-app ack() response stored
after ack
before fakeSay
after fakeSay
[DEBUG]  bolt-app event processed
[DEBUG]  bolt-app stored response sent
action HTTP response received {}

In this run, for the events API request, the stored response sent log occurs after the event processed log as expected. And for the action request, the same is true.

Update: Added a await fakeSay() call to the listeners, which contains a randomized delay, in an effort to find the right conditions to trigger the issue. Tried on node v10.19.0 and v12.13.0. Still have not been able to reproduce. Sample program and outputs updated.

@aoberoi
Copy link
Contributor Author

aoberoi commented Apr 4, 2020

I'm going to keep trying to reproduce this issue. My next attempt will be to put the code that makes the requests to the Bolt app in a separate process from the Bolt app itself. I think it's interesting that the HTTP response received {} log lines were always after the event processed lines and not sooner, directly after the response sent lines. Maybe this is a side-effect of both parts sharing the same event loop, but I want to be sure.

@seratch seratch added this to the v2.0 milestone Apr 4, 2020
@seratch seratch added the bug M-T: confirmed bug report. Issues are confirmed when the reproduction steps are documented label Apr 4, 2020
@aoberoi
Copy link
Contributor Author

aoberoi commented Apr 4, 2020

@seratch i'm going to remove the bug label until we have solid reproduction steps.

i used needs info because i still haven't been able to do reproduce locally. if you can share a sample program that demonstrates the issue locally, happy to apply bug then!

@aoberoi aoberoi removed the bug M-T: confirmed bug report. Issues are confirmed when the reproduction steps are documented label Apr 4, 2020
@aoberoi
Copy link
Contributor Author

aoberoi commented Apr 4, 2020

also, v2.0 is released so i think i should close that milestone too.

@aoberoi aoberoi removed this from the v2.0 milestone Apr 4, 2020
@seratch
Copy link
Member

seratch commented Apr 4, 2020

My intention of putting v2.0 milestone is marking the changes that will be included 2.0 patch releases but it may be a bit irregular way of the milestone's usage. I agree that we can close it as we've already released the initial version of the series.

@seratch
Copy link
Member

seratch commented Apr 4, 2020

@aoberoi Here is a reproducer.

if you can share a sample program that demonstrates the issue locally, happy to apply bug then!

I can put the bug label again now.

Update: I've figured out axios's http requests (and everything after that in the same listener) don’t join the async/await cycle. You can see that by enabling debug logging.
https://github.com/slackapi/node-slack-sdk/blob/%40slack/web-api%405.8.0/packages/web-api/src/WebClient.ts#L699-L704

index.js

const { LogLevel } = require("@slack/logger");
const logLevel = process.env.SLACK_LOG_LEVEL || LogLevel.INFO;

const { App } = require("@slack/bolt");
const app = new App({
  logLevel: logLevel,
  signingSecret: process.env.SLACK_SIGNING_SECRET,
  token: process.env.SLACK_BOT_TOKEN,
  processBeforeResponse: true,
});

app.use(async (args) => {
  args.logger.info("*** middleware ***");
  const result = await args.next();
  args.logger.info("after next() in a middleware");
  return result;
});

app.event("app_mention", async ({ logger, event, say }) => {
  logger.info("*** app_mention event ***");
  const result = await say({ text: `:wave: <@${event.user}> Hi there!` });
  logger.info("after say");
  return result;
});

app.command("/open-modal", async ({ logger, client, ack, body }) => {
  logger.info("*** slash command ***");
  try {
    const res = await client.views.open({
      "trigger_id": body.trigger_id,
      "view": {
        "type": "modal",
        "callback_id": "task-modal",
        "title": {
          "type": "plain_text",
          "text": "Create a task"
        },
        "submit": {
          "type": "plain_text",
          "text": "Submit"
        },
        "close": {
          "type": "plain_text",
          "text": "Cancel"
        },
        "blocks": [
          {
            "type": "input",
            "block_id": "input-title",
            "element": {
              "type": "plain_text_input",
              "action_id": "input"
            },
            "label": {
              "type": "plain_text",
              "text": "Title"
            },
            "optional": false
          }
        ]
      }
    });
    logger.info("after views.open");
    await ack();
    logger.info("after ack");
  } catch (e) {
    logger.error("views.open error:\n\n" + JSON.stringify(e, null, 2) + "\n");
    await ack(`:x: Failed to open a modal due to *${e.code}* ...`);
  }
});

app.view("task-modal", async ({ logger, client, body, ack }) => {
  logger.info("*** view submission ***");
  await ack();
  logger.info("after ack");
});

(async () => {
  await app.start(process.env.PORT || 3000);
  console.log("⚡️ Bolt app is running!");
})();

Here is another version with promise chain style.

app.use(async (args) => {
  args.logger.info("*** middleware ***");
  return args.next().then(result => {
    args.logger.info("after next() in a middleware");
    return result;
  })
});

app.event("app_mention", async ({ logger, event, say }) => {
  logger.info("*** app_mention event ***");
  return say({ text: `:wave: <@${event.user}> Hi there!` })
    .then(() => logger.info("after say"));
});

app.command("/open-modal", async ({ logger, client, ack, body }) => {
  logger.info("*** slash command ***");
  return client.views.open({
    "trigger_id": body.trigger_id,
    "view": {
      "type": "modal",
      "callback_id": "task-modal",
      "title": {
        "type": "plain_text",
        "text": "Create a task"
      },
      "submit": {
        "type": "plain_text",
        "text": "Submit"
      },
      "close": {
        "type": "plain_text",
        "text": "Cancel"
      },
      "blocks": [
        {
          "type": "input",
          "block_id": "input-title",
          "element": {
            "type": "plain_text_input",
            "action_id": "input"
          },
          "label": {
            "type": "plain_text",
            "text": "Title"
          },
          "optional": false
        }
      ]
    }
  })
    .then(() => logger.info("after views.open"))
    .then(() => ack())
    .then(() => logger.info("after ack"))
    .catch((e) => {
      logger.error("views.open error:\n\n" + JSON.stringify(e, null, 2) + "\n");
      return ack(`:x: Failed to open a modal due to *${e.code}* ...`);
    });
});

app.view("task-modal", async ({ logger, client, body, ack }) => {
  logger.info("*** view submission ***");
  return ack().then(() => logger.info("after ack"));
});

package.json

{
  "name": "bolt-starter",
  "version": "0.0.1",
  "description": "Bolt app starter",
  "main": "index.js",
  "scripts": {
    "local": "node_modules/.bin/nodemon index.js",
    "start": "node index.js"
  },
  "repository": {
    "type": "git",
    "url": "[email protected]:seratch/bolt-starter.git"
  },
  "keywords": [
    "Slack",
    "Bolt"
  ],
  "author": "@seratch",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/seratch/bolt-starter/issues"
  },
  "homepage": "https://github.com/seratch/bolt-starter#readme",
  "dependencies": {
    "@slack/bolt": "^2.0.0",
    "dotenv": "^8.2.0"
  },
  "devDependencies": {
    "nodemon": "^2.0.1"
  }
}

node_modules/@slack/bolt/dist/ExpressReceiver.js

Modified node_modules/\@slack/bolt/dist/ExpressReceiver.js a bit.

            await ((_a = this.bolt) === null || _a === void 0 ? void 0 : _a.processEvent(event));
            console.log(`processBeforeResponse: ${this.processBeforeResponse}, stored response: ${storedResponse}`); // ADDED
            if (storedResponse !== undefined) {
                if (typeof storedResponse === 'string') {
                    res.send(storedResponse);
                }
                else {
                    res.json(storedResponse);
                }
                console.log(`stored response sent`); // ADDED
              }

outputs

app_mention events

[INFO]  bolt-app *** middleware ***
[INFO]  bolt-app *** app_mention event ***
processBeforeResponse: true, stored response: 
stored response sent
[INFO]  bolt-app after say
[INFO]  bolt-app after next() in a middleware

slash command + views.open API method call

[INFO]  bolt-app *** middleware ***
[INFO]  bolt-app *** slash command ***
processBeforeResponse: true, stored response: undefined
[INFO]  bolt-app after views.open
[INFO]  bolt-app after ack
[INFO]  bolt-app after next() in a middleware

view_submission

[INFO]  bolt-app *** middleware ***
[INFO]  bolt-app *** view submission ***
[INFO]  bolt-app after ack
[INFO]  bolt-app after next() in a middleware
processBeforeResponse: true, stored response: 
stored response sent

@seratch
Copy link
Member

seratch commented Apr 7, 2020

As I shared above, the main issue we're still tackling is that processBeforeRepsonse: true doesn't work when a Bolt listener function has WebClient calls inside.

HTTP requests by axios always run separately from an async listener/middleware function. I'm still not sure if there is a way to take control from the Bolt framework side (even not sure if it's specific to axios or it can happen with other 3rd parties yet).

In my understanding, there are three options here:

@aoberoi @stevengill @shaydewael
I'd like to know your thoughts on this.

I prefer 0️⃣ > 2️⃣ > 1️⃣ but I can agree with any decision. For 0️⃣, I don't have the bandwidth to take it this week, so that I would like someone else (any contributions from the community are of course welcome!) to do a deep dive.

@aoberoi
Copy link
Contributor Author

aoberoi commented Apr 8, 2020

Thanks for making a reproducing case. I've run it myself and verified. bug indeed!

I also agree with the priority of these solutions. However, I think the only way to achieve 2️⃣ without a breaking API change is through an additional option on App. The problem with that is there will be some example code in the wild that depends on that option being set, and this will cause confusion. Therefore I think option 1️⃣ might be more feasible for the short term and we can switch to 2️⃣ at the next semver:major. I'm going to investigate the feasibility of 0️⃣ so that we don't have to make that compromise 🙇

@aoberoi aoberoi added bug M-T: confirmed bug report. Issues are confirmed when the reproduction steps are documented and removed needs info An issue that is claimed to be a bug and hasn't been reproduced, or otherwise needs more info labels Apr 8, 2020
@aoberoi aoberoi changed the title Setting processBeforeResponse to true and responses are still sent before listener completes processBeforeRepsonse: true doesn't work when a Bolt listener function has WebClient calls inside Apr 8, 2020
@seratch
Copy link
Member

seratch commented Apr 9, 2020

The problem with that is there will be some example code in the wild that depends on that option being set, and this will cause confusion.

To be clear, for 2️⃣, I think we can add a new option with a default value that is backward compatible (if I don't miss something). Let's say the option is autoEventAckEnabled and the default value is true. With this way, we won't bring any breaking changes to existing users unless those users intentionally set the option as false. From this perspective, I don't see any difference between the processBeforeResponse and this one.

@aoberoi
Copy link
Contributor Author

aoberoi commented Apr 9, 2020

I agree that we can add an option and it can remain backwards compatible. The problem is that example code such as the following won't work for anyone who sets the option to false:

app.event('app_mention', async ({ event, say }) => {
  await say(`:wave: <@${event.user}> Hi there!`);
});

Likewise, sample code like the following won't work for anyone who sets the option to true:

app.event('app_mention', async ({ event, ack, say }) => {
  await ack();
  await say(`:wave: <@${event.user}> Hi there!`);
});

This results in fragmentation in our docs, sample code, and other places. This is feasible, but in my opinion makes this solution a much less attractive option.

@aoberoi
Copy link
Contributor Author

aoberoi commented Apr 9, 2020

I've been trying to narrow down this situation and I'm seeing some very strange (non-deterministic) results.

I'm running a pretty minimal sample program:

const { App, LogLevel } = require('./dist');

const processBeforeResponse = true;
const signingSecret = process.env.SLACK_SIGNING_SECRET;

const app = new App({
  signingSecret,
  processBeforeResponse,
  logLevel: LogLevel.DEBUG,
  ignoreSelf: false,
  authorize() { return Promise.resolve({ }); },
});

app.event('app_mention', async ({ event, say }) => {
  console.log('*** app_mention event ***');
  try {
    await say(`:wave: <@${event.user}> Hi there!`);
  } catch (error) {
    // say will not work without a real token, so catch the error to allow the listener to resume
    console.error(error);
  }
  console.log('after say');
});

app.event('app_mention_fake', async ({ event, say }) => {
  console.log('*** app_mention_fake event ***');
  await fakeSay();
  console.log('after fakeSay');
});

app.event('app_mention_sync', async () => {
  console.log('*** app_mention_sync event ***');
});

(async () => {
  await app.start(8080);
  console.log('⚡️ Bolt app is running!');
})();

function fakeSay() {
  return new Promise((resolve) => {
    // random delay between 1-6 seconds
    setTimeout(resolve, (Math.random() * 5000) + 1000);
  });
}

I also have the stored response sent log line added to ExpressReceiver. I'm also sending HTTP requests to this program myself in another program, instead of using requests sent from Slack (trying to isolate the problem, and its easier to re-trigger/reproduce).

In the first event listener (app_mention), the real say() function is called, which is implemented through WebClient and axios. In the second event listener (app_mention_fake), say is instead replaced with fakeSay(), which I know correctly returns a Promise that is not dangling or disconnected from the event loop.

If what we landed on earlier was true (axios's returned Promise is dangling and not properly connected to the event loop), then I'd expect to see the after say log after the stored response sent log, and the after fakeSay log before the stored response sent log. Instead, I see the following:

[DEBUG]  WebClient:0 initialized
⚡️ Bolt app is running!
[DEBUG]  bolt-app ack() begin
[DEBUG]  bolt-app ack() response stored
[DEBUG]  bolt-app Conversation context not loaded: Conversation not found
*** app_mention event ***
[DEBUG]  WebClient:0 apiCall('chat.postMessage') start
[DEBUG]  WebClient:0 will perform http request
[DEBUG]  bolt-app stored response sent
[DEBUG]  bolt-app ack() begin
[DEBUG]  bolt-app ack() response stored
[DEBUG]  bolt-app Conversation context not loaded: Conversation not found
*** app_mention_fake event ***
[DEBUG]  bolt-app stored response sent
[DEBUG]  WebClient:0 http response received
Error: An API error occurred: not_authed
    at Object.platformErrorFromResult (/Users/ankur/Developer/bolt/node_modules/@slack/web-api/dist/errors.js:50:33)
    at WebClient.apiCall (/Users/ankur/Developer/bolt/node_modules/@slack/web-api/dist/WebClient.js:485:28)
    at processTicksAndRejections (internal/process/task_queues.js:93:5)
    at async /Users/ankur/Developer/bolt/temp3.js:18:5
    at async Array.<anonymous> (/Users/ankur/Developer/bolt/dist/middleware/builtin.js:217:9)
    at async Array.exports.onlyEvents (/Users/ankur/Developer/bolt/dist/middleware/builtin.js:63:5) {
  code: 'slack_webapi_platform_error',
  data: {
    ok: false,
    error: 'not_authed',
    response_metadata: { acceptedScopes: [Array] }
  }
}
after say
after fakeSay

For both event listeners, stored response sent is occurring before say or fakeSay complete. This indicates the problem is not in say(), WebClient, or axios.

The confusing part is these results directly conflict with the results of the test I did earlier.

@aoberoi
Copy link
Contributor Author

aoberoi commented Apr 9, 2020

One more data point: when I remove the HTTP client code (boltRequester) from the earlier test, it also shows failing results matching the test I just posted above. It seems like putting the HTTP client and server in the same process somehow influences the ordering (maybe some synchronization deep in http?).

My next step will be to do some breakpoint debugging to understand the execution order a little better.

@aoberoi
Copy link
Contributor Author

aoberoi commented Apr 10, 2020

I've figured it out! It was much simpler than I thought it would be. The conversationContext global middleware was not correctly updated for Bolt v2 breaking changes.

In the following code, the return value of next() is not awaited, and the Promise returned by the expression is not returned by the middleware.

https://github.com/slackapi/bolt/blob/de943944f31fdc1462fe2236c56f87c8f293de7c/src/conversation-store.ts#L64-L73

This led to a "fork" of the middleware chain. One side of the fork held all the following middleware processing. The other side resolved, allowing the Promise returned by App.processEvent() to resolve, and the receiver to send the HTTP response.

I'm preparing a PR with a fix now.

@aoberoi
Copy link
Contributor Author

aoberoi commented Apr 10, 2020

and as for why my test case above passed... it's annoyingly simple. my simulated Events API request and block action request didn't contain a conversation ID. so the conversationContext global middleware wasn't trying to query the store and never reached the lines above.

@seratch
Copy link
Member

seratch commented Apr 10, 2020

Thanks for working on this and I'm happy to know you've figured out the cause! The above explanation is very clear and that makes sense a lot to me.

I haven't verified if applying the fix on conversation store fixes the issue yet. If you need someone to double-check, I can do that. But if you're already confident enough, let's ship the fix as soon as possible.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug M-T: confirmed bug report. Issues are confirmed when the reproduction steps are documented
Projects
None yet
3 participants