MV3: `async chrome.runtime.onMessage` listener always returns undefined instead of resolved value

139 views
Skip to first unread message

Adnan Khan

unread,
Feb 10, 2026, 7:13:13 PMFeb 10
to Chromium Extensions

We had compalints from customer that some of the features of extension are not working anymore, upon checking, it seems something with message passing has changed that regresses quite features. These features worked just fine from last 2 years but suddenly broken.

I tried to atleast reproduce one method that we are using actively in our extension but not working in standalone extension too

It is for chrome.runtime.onMessage, exactly as documented for Chrome 144+.

The value returned from an async onMessage listener is always received as undefined in the sender, even though the listener resolves to a concrete value.



Version 145.0.7632.46 (Official Build) (64-bit)
OS: Windows



Manifest.json
{
  "manifest_version": 3,
  "name": "Async Message Test",
  "version": "1.0",
  "background": {
    "service_worker": "background.js"
  },
  "content_scripts": [
    {
      "matches": ["https://*/*"],
      "js": ["content_script.js"]
    }
  ]
}


background.js
chrome.runtime.onMessage.addListener(async () => {
  await new Promise(resolve => setTimeout(resolve, 1000));
  return "OK";
});


content_script.js
(async () => {
  const response = await chrome.runtime.sendMessage("test");
  console.log("response:", response);
})();


Let me know if I have to report this into proper channel but I'm looking if I am doing something wrong to fix this issue in my current production extension or I can report this in proper channel as well, thanks!

Adnan Khan

unread,
Feb 11, 2026, 5:27:03 AMFeb 11
to Daniel MacArthur, chromium-...@chromium.org
Sorry, had typo in sentence earlier.

But documentation says otherwise
https://developer.chrome.com/docs/extensions/develop/concepts/messaging

From documentation:
You can also declare a listener as async to return a promise:
chrome.runtime.onMessage.addListener(async function(message, sender) {
  const response = await fetch('https://example.com');
  if (!response.ok) {
    // rejects the promise returned by `async function`.
    throw new Error(`Fetch failed: ${response.status}`);
  }
  // resolves the promise returned by `async function`.
  return {statusCode: response.status};
});

On Wed, Feb 11, 2026 at 3:23 PM Adnan Khan <adnankhank...@gmail.com> wrote:
Both documentation says otherwise
https://developer.chrome.com/docs/extensions/develop/concepts/messaging

From documentation:
You can also declare a listener as async to return a promise:
chrome.runtime.onMessage.addListener(async function(message, sender) {
  const response = await fetch('https://example.com');
  if (!response.ok) {
    // rejects the promise returned by `async function`.
    throw new Error(`Fetch failed: ${response.status}`);
  }
  // resolves the promise returned by `async function`.
  return {statusCode: response.status};
});

On Wed, Feb 11, 2026 at 2:23 PM Daniel MacArthur <d.mac...@everway.com> wrote:
your issue is in background.js, as of chrome 144+ you can no long have async in the runtime listener:


background.js
chrome.runtime.onMessage.addListener(async () => {
  await new Promise(resolve => setTimeout(resolve, 1000));
  return "OK";
});

this would fix your example:

manifest.json

{
  "manifest_version": 3,
  "name": "Async Message Test",
  "version": "1.0",
  "background": {
    "service_worker": "background.js"
  },
  "content_scripts": [
    {
      "matches": ["https://*/*"],
      "js": ["content_script.js"]
    }
  ]
}

background.js 
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
  // Return true to signal we will send a response asynchronously

  new Promise(resolve => setTimeout(resolve, 1000))
    .then(() => {
      sendResponse("OK");
    })
    .catch(error => {
      console.error('Error in message listener:', error);
      sendResponse({ error: error.message });
    });
 
  return true; // Keep channel open for async response

});

content_script.js
(async () => {
  try {

    const response = await chrome.runtime.sendMessage("test");
    console.log("response:", response);
  } catch (error) {
    console.error("Error sending message:", error);
  }
})();

Juraj M.

unread,
Feb 11, 2026, 6:00:03 AMFeb 11
to Chromium Extensions, Adnan Khan, Daniel MacArthur
This new feature (promise reply) was reverted shortly after deploy:

Adnan Khan

unread,
Feb 11, 2026, 6:27:47 AMFeb 11
to Chromium Extensions, Juraj M., Adnan Khan, Daniel MacArthur
That makes sense, thank you all!

Daniel MacArthur

unread,
Feb 12, 2026, 9:39:27 AMFeb 12
to Chromium Extensions, Adnan Khan
your issue is in background.js, as of chrome 144+ you can no long have async in the runtime listener:

background.js
chrome.runtime.onMessage.addListener(async () => {
  await new Promise(resolve => setTimeout(resolve, 1000));
  return "OK";
});

this would fix your example:

manifest.json

{
  "manifest_version": 3,
  "name": "Async Message Test",
  "version": "1.0",
  "background": {
    "service_worker": "background.js"
  },
  "content_scripts": [
    {
      "matches": ["https://*/*"],
      "js": ["content_script.js"]
    }
  ]
}

background.js 
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
  // Return true to signal we will send a response asynchronously
  new Promise(resolve => setTimeout(resolve, 1000))
    .then(() => {
      sendResponse("OK");
    })
    .catch(error => {
      console.error('Error in message listener:', error);
      sendResponse({ error: error.message });
    });
 
  return true; // Keep channel open for async response
});

content_script.js
(async () => {
  try {

    const response = await chrome.runtime.sendMessage("test");
    console.log("response:", response);
  } catch (error) {
    console.error("Error sending message:", error);
  }
})();


On Wednesday, 11 February 2026 at 00:13:13 UTC Adnan Khan wrote:
Reply all
Reply to author
Forward
0 new messages