{"record":{"id":"230f94f987bbd3e2","repo":"alyssaxuu/screenity","slug":"failed-to-create-bunny-video","errorCode":null,"errorMessage":"Failed to create Bunny video","messagePattern":"Failed to create Bunny video","errorType":"http","errorClass":"Error","httpStatus":null,"severity":"critical","filePath":"src/pages/CloudRecorder/bunnyTusUploader.js","lineNumber":1002,"sourceCode":"        });\n\n        if (!res || !res.ok) {\n          // PROBE: persist failure shape so post-mortem can see what\n          // came back. Removed once Playwright mock race is identified.\n          try {\n            await chrome.storage.local.set({\n              probeBunnyCreateFail: {\n                ts: Date.now(),\n                resPresent: !!res,\n                status: res?.status ?? null,\n                type: res?.type ?? null,\n                url: res?.url ?? null,\n                redirected: res?.redirected ?? null,\n                ok: res?.ok ?? null,\n              },\n            });\n          } catch {}\n          throw new Error(\"Failed to create Bunny video\");\n        }\n        const data = await res.json();\n        this.videoId = data.videoId;\n        this.mediaId = data.mediaId;\n        // The server now inlines the TUS upload signature in this\n        // response (data.tusAuth). When present, stash it so the\n        // immediate-next refreshTusAuth() can skip its GET round-trip.\n        // Saves ~300-1000ms per uploader in dev. Falls back to the\n        // GET path when tusAuth is missing (older server / resume).\n        if (data?.tusAuth?.signature && data.tusAuth.expires && data.tusAuth.libraryId) {\n          this._inlinedTusAuth = {\n            signature: data.tusAuth.signature,\n            expires: data.tusAuth.expires,\n            libraryId: data.tusAuth.libraryId,\n          };\n        }\n        await this.persistVideoMap({\n          projectId,","sourceCodeStart":984,"sourceCodeEnd":1020,"githubUrl":"https://github.com/alyssaxuu/screenity/blob/512606387b8d07dda5e63bb428bd063f0a2a3ed0/src/pages/CloudRecorder/bunnyTusUploader.js#L984-L1020","documentation":"initialize() creates the Bunny video by POSTing to `${API_BASE}/bunny/videos` with the Screenity bearer token. The call is retried up to 3 times for transient failures (network errors, 5xx, 429), but if the final response is missing or not ok, initialize() throws this error after persisting a failure probe (probeBunnyCreateFail) to chrome.storage.local for post-mortem. It means the backend refused or failed to create the video, so no upload target exists.","triggerScenarios":"POST /bunny/videos returned a non-ok status (e.g. 400 validation, 401 expired token, 429 rate limit, 5xx after 3 attempts) or all fetch attempts threw network errors, leaving res null or res.ok false on the create-new-video path of initialize().","commonSituations":"Expired/invalid screenityToken producing 401; Bunny library quota or API key problems surfacing as 4xx/5xx; offline or blocked network (CSP, proxy) so fetch throws every attempt; backend deployment regression; server-side rate limiting during parallel uploads.","solutions":["Inspect chrome.storage.local.probeBunnyCreateFail to see the recorded status/ok/url and identify the HTTP failure mode.","If status is 401, refresh the Screenity token (re-authenticate) and retry; the uploader itself doesn't retry 4xx by design.","If status is 5xx/429 or res is null, wait briefly and retry initialize() — transient failures are only retried 3 times with short backoff (500ms, 1000ms).","Verify network reachability of API_BASE and that the extension host permissions allow the request.","Check backend logs for the POST /bunny/videos handler to find the server-side cause (Bunny credentials, library limits, validation)."],"exampleFix":"// before\nawait uploader.initialize({ projectId, sceneId, type });\n// after\ntry {\n  await uploader.initialize({ projectId, sceneId, type });\n} catch (err) {\n  if (String(err?.message).includes(\"Failed to create Bunny video\")) {\n    const probe = (await chrome.storage.local.get(\"probeBunnyCreateFail\")).probeBunnyCreateFail;\n    if (probe?.status === 401) await reauthenticateScreenity();\n    else await new Promise(r => setTimeout(r, 2000)); // back off for transient\n  }\n  await uploader.initialize({ projectId, sceneId, type });\n}","handlingStrategy":"retry","validationCode":"const { authenticated, screenityToken } = await getAuthState();\nif (!authenticated || !screenityToken) throw new Error('Sign in before starting a cloud recording');","typeGuard":null,"tryCatchPattern":"try {\n  await uploader.initialize({ projectId, sceneId, type });\n} catch (err) {\n  if (String(err?.message).includes('Failed to create Bunny video')) {\n    const probe = (await chrome.storage.local.get('probeBunnyCreateFail')).probeBunnyCreateFail;\n    const transient = probe == null || probe.status == null || probe.status >= 500 || probe.status === 429;\n    if (transient) {\n      await sleep(2000);\n      return uploader.initialize({ projectId, sceneId, type });\n    }\n    if (probe?.status === 401) await reauthenticateScreenity();\n  }\n  throw err;\n}","preventionTips":["Check probeBunnyCreateFail in storage after failures — it records the HTTP status shape for triage.","Refresh the Screenity token proactively before long recording sessions to avoid 401s.","Distinguish transient (5xx/429/network) from permanent (4xx) failures; only auto-retry the former.","Monitor backend /bunny/videos error rates and Bunny library quota usage.","Ensure extension host_permissions include API_BASE so fetch isn't blocked."],"tags":["network","api","backend","http-error","bunny"],"backgroundTag":"api-request-failed","analyzedSha":"512606387b8d07dda5e63bb428bd063f0a2a3ed0","analyzedAt":"2026-09-02T20:59:09.419Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T02:17:09.455Z"}