alyssaxuu/screenity · error · Error

offscreen tab stream failed

Error message

offscreen tab stream failed

What it means

getStreamID() runs in an offscreen document where chrome.tabCapture is not callable, so it asks the service worker via an 'offscreen-request-stream' message. This error is thrown when the SW response is absent, not ok, or lacks a streamId — meaning tab capture could not be granted through the SW delegate.

Source

Thrown at src/pages/CloudRecorder/CloudRecorder.jsx:8146

    } catch (err) {
      sendRecordingError("Failed to setup streaming: " + err.message);
    }
  };

  const getStreamID = async (id) => {
    try {
      let streamId;
      if (IS_OFFSCREEN_HOST) {
        // chrome.tabCapture is not callable from offscreen; delegate to SW.
        const response = await chrome.runtime
          .sendMessage({
            type: "offscreen-request-stream",
            mode: "tab",
            targetTabId: id,
          })
          .catch((err) => ({ ok: false, error: String(err) }));
        if (!response?.ok || !response.streamId) {
          throw new Error(response?.error || "offscreen tab stream failed");
        }
        streamId = response.streamId;
      } else {
        streamId = await chrome.tabCapture.getMediaStreamId({
          targetTabId: id,
        });
      }
      tabID.current = streamId;
    } catch (err) {
      sendRecordingError("Failed to get stream ID: " + err.message);
    }
  };

  useEffect(() => {
    if (!IS_IFRAME_CONTEXT) return;

    const sendReady = () => {
      window.parent.postMessage(

View on GitHub (pinned to 512606387b)

Solutions

  1. Check the SW responded at all: a rejected sendMessage means the service worker is dead — ensure it's alive/awake before the request
  2. Verify tabCapture permission in the manifest and that the target tab is still open and capturable
  3. Confirm the SW's offscreen-request-stream handler calls chrome.tabCapture.getMediaStreamId({targetTabId}) and returns {ok:true, streamId}
  4. Retry once after SW wake-up, mirroring the sendOnce retry pattern used for scene creation
  5. Fall back to the non-offscreen path (chrome.tabCapture.getMediaStreamId directly) when not running in an offscreen host

Example fix

// before
if (!response?.ok || !response.streamId) {
  throw new Error(response?.error || "offscreen tab stream failed");
}
// after
if (!response?.ok || !response.streamId) {
  await new Promise(r => setTimeout(r, 300)); // let SW wake
  response = await chrome.runtime.sendMessage({ type: "offscreen-request-stream", mode: "tab", targetTabId: id }).catch(e => ({ ok: false, error: String(e) }));
}
if (!response?.ok || !response.streamId) {
  throw new Error(response?.error || "offscreen tab stream failed");
}
Defensive patterns

Strategy: fallback

Validate before calling

async function canRequestTabStream(tabId) {
  if (!chrome.runtime?.id) return false; // extension context invalidated
  const tab = await chrome.tabs.get(tabId).catch(() => null);
  return Boolean(tab && !tab.discarded);
}

Type guard

function streamResponseOk(res) {
  return typeof res === "object" && res !== null && res.ok === true && typeof res.streamId === "string" && res.streamId.length > 0;
}

Try / catch

try {
  const res = await chrome.runtime.sendMessage({ type: "offscreen-request-stream", mode: "tab", targetTabId: id });
  if (!streamResponseOk(res)) throw new Error(res?.error || "offscreen tab stream failed");
  streamId = res.streamId;
} catch (err) {
  // wake SW and retry once, then surface to user
  await pingServiceWorker();
  const res2 = await chrome.runtime.sendMessage({ type: "offscreen-request-stream", mode: "tab", targetTabId: id }).catch(e => ({ ok: false, error: String(e) }));
  if (!streamResponseOk(res2)) sendRecordingError("Failed to get stream ID: " + (res2?.error || err.message));
  else streamId = res2.streamId;
}

Prevention

When it happens

Trigger: chrome.runtime.sendMessage({type:'offscreen-request-stream', mode:'tab', targetTabId}) rejects (SW unavailable, message port closed) producing {ok:false,error:String(err)}, or the SW replies ok:false / without streamId because chrome.tabCapture.getMediaStreamId failed (tab not active, no tabCapture permission, capture already in progress).

Common situations: Service worker asleep or crashed so the message promise rejects with 'Could not establish connection', target tab closed or navigated before the request, chrome.tabCapture.getMediaStreamId returning undefined due to user gesture/activeTab requirements, or Manifest V3 offscreen document restrictions.

Related errors


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