JavaScript Error Monitoring

"Extension context invalidated": Fix This Chrome Error

This error fires when your extension reloads while content scripts keep running. Learn why orphaned scripts throw it and how to detect and recover cleanly.

If you've developed or monitored a Chrome extension, you've likely seen this error: Uncaught Error: Extension context invalidated. It appears in the console of web pages, seemingly random, and often points to a content script trying to talk to the extension's runtime. Unlike most JavaScript errors, this one has a very specific root cause—and it's actually a feature of how Chrome isolates extensions, not a bug in your code.

The good news: once you understand the mechanism, fixing it is straightforward. The error fires when an already-injected content script tries to use chrome.runtime APIs after the extension's runtime has been destroyed. This guide explains exactly when and why it happens, and how to prevent it from drowning your error tracker in noise while keeping your extension robust. For a broader look at extension error tracking setup, see the Chrome extension error monitoring guide.

Why Content Scripts Become Orphans

Here's the lifecycle: when your extension is installed or started, Chrome injects your content scripts into all matching web pages. These scripts run in an isolated execution context within the page's DOM but maintain a live connection to your extension's runtime via chrome.runtime APIs. That connection is the bridge—it lets your content script send messages to the background service worker, read from storage, and respond to extension events.

But the extension's runtime isn't permanent. When you reload the extension (via chrome://extensions), push an update (auto-updates for real users), disable it, or even when the service worker terminates unexpectedly, that runtime is destroyed. The content scripts, however, remain injected in the page. They're still there, still listening to DOM events, but the runtime they were talking to is gone.

Any call to chrome.runtime.sendMessage(), chrome.storage.local.get(), or any other chrome API throws immediately: Extension context invalidated. The content script is now an orphan—alive but disconnected.

When Does This Error Actually Occur?

During development: Every time you reload your extension (via the extension UI or by running npm run ext:watch), all active tabs get orphaned content scripts. The old scripts keep running until the user reloads the page.

For real users: Auto-updates are silent but deadly. A user is browsing with your extension, you push a new version, the extension updates in the background, and boom—their currently active pages get orphaned content scripts. They might click a button that triggers a content script message, and the error fires.

On disable/enable: If a user disables and re-enables your extension (or another extension conflicts and causes a disable), the same orphan state occurs.

Service worker termination: In MV3, service workers are not always-on. Chrome terminates them after inactivity. This is different from the context-invalidated problem—when the service worker restarts, it's fine—but if a content script tries to communicate during a service worker sleep and that sleep lasts too long, it can appear similar.

Service worker termination vs. context invalidation are often confused. A terminated service worker can respawn and recover; an orphaned content script cannot. The orphaning happens when the extension's entire runtime is destroyed (update, reload, disable), not just when the service worker sleeps. This distinction matters for your fix strategy.

Fix #1: Detect the Invalidation Up Front

The simplest guard: before making any chrome.runtime.* call, check if the extension context is still alive. The most reliable check is chrome.runtime?.id:

// content.js
function isExtensionContextValid() {
  try {
    return chrome.runtime?.id !== undefined;
  } catch (e) {
    return false;
  }
}

// Before any chrome API call:
if (!isExtensionContextValid()) {
  console.warn("Extension context is invalidated. Page reload required.");
  return;
}

chrome.runtime.sendMessage({ type: "DO_SOMETHING" });

This check is cheap and works reliably. If the context is gone, chrome.runtime.id is undefined (not an error, just undefined). If you try to access any other chrome API property without this guard, it throws.

Fix #2: Wrap Runtime Calls with Try/Catch and Graceful Failure

Never assume a chrome.runtime call will succeed. Wrap them:

// content.js
function sendMessageSafely(message) {
  if (!chrome.runtime?.id) {
    console.warn("Extension context invalidated; skipping message.");
    return Promise.reject(new Error("Extension context invalidated"));
  }

  return chrome.runtime.sendMessage(message).catch((error) => {
    if (error.message.includes("Extension context invalidated")) {
      console.warn("Extension context lost during send. Cleaning up listeners.");
      removeAllListeners();
      clearAllTimers();
      return null;
    }
    // Re-throw other errors
    throw error;
  });
}

function removeAllListeners() {
  // Clean up DOM listeners, mutation observers, etc.
  // so they don't keep firing and throwing errors
}

function clearAllTimers() {
  // Clear any setInterval or setTimeout calls
}

The key: once you detect the error, stop the bleeding. Remove all listeners and observers so your orphaned content script stops throwing repeatedly. This pattern mirrors broader global error handling strategies for cleaning up after failures.

Fix #3: Show the User a Graceful Message

For user-facing extensions (those with visible UI), silently failing is poor UX. Show a small, non-intrusive notification:

// content.js
function showReloadPrompt() {
  const banner = document.createElement("div");
  banner.style.cssText = `
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    background: #fff3cd;
    color: #856404;
    padding: 12px;
    font-size: 14px;
    border-bottom: 1px solid #ffc107;
    z-index: 999999;
  `;
  banner.textContent =
    "The extension was updated. Reload the page to continue using it.";
  document.body.insertBefore(banner, document.body.firstChild);

  // Auto-remove after 10 seconds if user doesn't see it
  setTimeout(() => banner.remove(), 10000);
}

// When you detect invalidation:
if (!chrome.runtime?.id) {
  showReloadPrompt();
}

Users can then reload the page (Cmd/Ctrl+R), and a fresh content script will be injected. This is much better than a silent error.

Fix #4: Reconnect Pattern for Long-Lived Ports

If your extension uses chrome.runtime.connect() to open a persistent port to the background, monitor it for disconnection:

// content.js
let port = null;

function reconnectPort() {
  if (!chrome.runtime?.id) {
    console.warn("Extension context invalidated; cannot reconnect.");
    return;
  }

  try {
    port = chrome.runtime.connect({ name: "content_script_port" });

    port.onDisconnect.addListener(() => {
      console.log("Port disconnected");
      port = null;
      // Attempt to reconnect after a delay
      setTimeout(reconnectPort, 1000);
    });

    port.onMessage.addListener((msg) => {
      // Handle messages from background
    });
  } catch (err) {
    console.error("Failed to connect:", err);
    port = null;
  }
}

// Initialize on load
reconnectPort();

// Use the port safely:
function sendViaPort(message) {
  if (port) {
    try {
      port.postMessage(message);
    } catch (err) {
      console.error("Port send failed:", err);
      port = null;
      reconnectPort();
    }
  }
}

Note: reconnection only works if the extension is still alive. If the context is truly invalidated, reconnection attempts will fail—so this is best used as a graceful fallback, not a recovery mechanism.

Fix #5: Reduce Dev Pain with Tab Reloads

During development, constantly reloading your extension is normal. To spare yourself the "Extension context invalidated" noise, reload the tab automatically after reloading the extension:

# If using webpack/esbuild dev server with a watch script:
npm run ext:watch
# After the build, reload the extension UI, then:
# Run this in a separate terminal to reload all extension tabs:
node -e "
const fs = require('fs');
const path = require('path');
// Use chrome debugger protocol to find and reload tabs
// Or, in your background.js:
"

// Better: in your background.js, listen for runtime reload events:
chrome.runtime.onInstalled.addListener((details) => {
  if (details.reason === 'update' || details.reason === 'install') {
    chrome.tabs.query({}, (tabs) => {
      tabs.forEach((tab) => {
        if (tab.url && tab.url.startsWith('http')) {
          chrome.tabs.reload(tab.id);
        }
      });
    });
  }
});

This isn't a fix for the error itself, but it drastically cuts down on seeing it during active development.

Fix #6: Filter It Out of Your Error Tracker

This is crucial: "Extension context invalidated" is expected behavior in development and during updates. If you're tracking errors with error monitoring, this error will fire constantly and drown out real issues, contributing to alert fatigue.

Use a beforeSend hook to filter it out:

// In your content script or background script error handling:
import * as Sentry from "@sentry/browser";

Sentry.init({
  dsn: "https://<your-key>@light-trace.robomiri.com/1",
  beforeSend(event, hint) {
    const error = hint.originalException;
    if (
      error &&
      (error.message.includes("Extension context invalidated") ||
        error.message.includes("chrome.runtime is not available"))
    ) {
      // Log it locally for debugging, but don't send to your tracker
      console.debug("[Orphaned content script]", error.message);
      return null; // Ignore this error
    }
    return event;
  },
});

Alternatively, if you do want to track it, group it under a single fingerprint so one issue represents all orphaned-script incidents—then mute that issue in your dashboard. Use error grouping and fingerprinting to consolidate similar errors:

beforeSend(event, hint) {
  if (hint.originalException?.message?.includes("Extension context invalidated")) {
    event.fingerprint = ["extension_context_invalidated"]; // Group all under one
  }
  return event;
}

This keeps your error dashboard clean while still capturing the signal.

In production, "Extension context invalidated" is often unactionable. Users need to reload the page, which they typically do on their own. The real value is monitoring whether a particular version update causes other errors due to the invalidation cascade—so filter this specific error but watch for correlations with your releases. Understanding the difference between expected and unexpected errors is key to browser JavaScript error tracking hygiene.

Testing Your Fix

To test your orphan-handling code without constantly reloading:

  1. Open DevTools in your extension's background service worker (chrome://extensions → click "inspect" on your extension).
  2. Trigger a page interaction that calls your content script (button click, form submission, etc.).
  3. Verify the message reaches the background and is handled.
  4. Reload the extension (click the reload button in chrome://extensions).
  5. In the same tab, trigger the same interaction again.
  6. You should see your graceful failure: no crash, no unhandled error, just a quiet log or user message.

Start tracking errors in minutes

If you're monitoring a Chrome extension with LightTrace, add these guards to keep your error dashboard focused on real issues—sign up free to start tracking errors with precision.

Fix your next production error faster

Point any Sentry SDK at LightTrace — free up to 5,000 events/month.