alyssaxuu/screenity · error · Error

content script injection failed for tab ${tabId}

Error message

content script injection failed for tab ${tabId}

What it means

sendMessageEnsuringContentScript first checks whether the extension content script is present in the target tab and, if not, injects it via injectContentScriptIntoTab. This error is thrown when the injection itself returns a falsy result, meaning the content script could not be established in the tab, so sending the message would hang or fail.

Source

Thrown at src/pages/Background/utils/executeScripts.js:64

  if (typeof tabId !== "number") return false;
  const contentScripts = chrome.runtime.getManifest().content_scripts || [];
  const files = contentScripts.flatMap((cs) => cs.js || []);
  if (files.length === 0) return false;
  try {
    await chrome.scripting.executeScript({ target: { tabId }, files });
    return true;
  } catch {
    return false;
  }
};

// Pings first: a blind send to a loading tab hangs instead of erroring, which
// is what made the popup take minutes. A resend would toggle the popup twice.
export const sendMessageEnsuringContentScript = async (tabId, message) => {
  const { sendMessageTab } = await import("../tabManagement");
  if (!(await hasContentScript(tabId))) {
    if (!(await injectContentScriptIntoTab(tabId))) {
      throw new Error(`content script injection failed for tab ${tabId}`);
    }
  }
  return sendMessageTab(tabId, message);
};

// Recently-used first. Sinks collapsed-group tabs without needing the
// tabGroups permission to identify them.
const injectionPriority = (tab, focusedWindowId) => {
  if (tab.active) return 0;
  if (focusedWindowId != null && tab.windowId === focusedWindowId) return 1;
  return 2;
};

// Skips the ~1MB re-parse when the script is already there. Only absence
// reads "Receiving end does not exist", so any other reply means it is.
// A timeout counts as absent. Injection is deduped by
// window.__screenityContentBootstrapped, so guessing wrong costs one parse.
const hasContentScript = async (tabId, timeoutMs = PING_TIMEOUT_MS) => {

View on GitHub (pinned to 512606387b)

Solutions

  1. Check the page URL before calling: skip restricted schemes (chrome://, edge://, chrome-extension://, about:) and surface a friendly 'this page is not supported' message instead of throwing.
  2. Verify manifest host_permissions / optional_host_permissions cover the target origin and that the 'scripting' permission is granted.
  3. Check tab status in the callback: if the tab is 'loading' or 'unloaded'/'discarded', wait for tabs.onUpdated status 'complete' or reload the tab, then retry once.
  4. Confirm the content script file path passed to chrome.scripting.executeScript is correct and the file is web-accessible/registered.
  5. Log the underlying chrome.runtime.lastError inside injectContentScriptIntoTab to see the real injection failure reason.

Example fix

// before
const ok = await injectContentScriptIntoTab(tabId);
if (!ok) {
  throw new Error(`content script injection failed for tab ${tabId}`);
}
// after
const tab = await chrome.tabs.get(tabId);
if (!/^https?:/.test(tab.url ?? "")) {
  throw new Error(`cannot inject into restricted page: ${tab.url}`);
}
await waitForTabComplete(tabId);
const ok = await injectContentScriptIntoTab(tabId);
if (!ok) {
  throw new Error(`content script injection failed for tab ${tabId}`);
}
Defensive patterns

Strategy: try-catch

Validate before calling

const tab = await chrome.tabs.get(tabId);
const injectable = /^https?:/.test(tab.url ?? "") && tab.status !== "unloaded";
if (!injectable) throw new Error(`Cannot inject content script into ${tab.url}`);

Type guard

function isInjectableTab(tab) {
  return typeof tab?.url === "string" && /^https?:/.test(tab.url);
}

Try / catch

try {
  await sendMessageEnsuringContentScript(tabId, message);
} catch (err) {
  if (String(err.message).startsWith("content script injection failed")) {
    showUnsupportedPageNotice(tabId);
  } else {
    throw err;
  }
}

Prevention

When it happens

Trigger: Calling sendMessageEnsuringContentScript(tabId, message) for a tab where hasContentScript returns false and injectContentScriptIntoTab also fails — e.g. restricted pages (chrome://, Web Store, PDF viewer), discarded/sleeping tabs, tabs without host permissions, or the tab closing/navigating mid-injection.

Common situations: User clicks the popup while the active tab is a browser-internal page; extension updated but old content script absent and scripting permission/host grants missing; tab was put to sleep by a tab-suspender extension; MV3 service worker woke up after the tab navigated.


AI-assisted analysis of alyssaxuu/screenity@512606387b (2026-09-02). Data as JSON: /api/errors/c233080a9d603d48. Report an issue: GitHub.