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
- Check the SW responded at all: a rejected sendMessage means the service worker is dead — ensure it's alive/awake before the request
- Verify tabCapture permission in the manifest and that the target tab is still open and capturable
- Confirm the SW's offscreen-request-stream handler calls chrome.tabCapture.getMediaStreamId({targetTabId}) and returns {ok:true, streamId}
- Retry once after SW wake-up, mirroring the sendOnce retry pattern used for scene creation
- 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
- Ping/keep the service worker alive before sending offscreen-request-stream
- Verify the target tab still exists and is capturable before requesting the streamId
- Declare tabCapture (and activeTab where needed) in the manifest
- Always return a structured {ok, streamId, error} from the SW handler so failures are distinguishable
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.