{"record":{"id":"955afb4605a001eb","repo":"alyssaxuu/screenity","slug":"failed-to-start-tus-upload-session","errorCode":null,"errorMessage":"Failed to start TUS upload session","messagePattern":"Failed to start TUS upload session","errorType":"http","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/pages/CloudRecorder/bunnyTusUploader.js","lineNumber":1214,"sourceCode":"  }\n\n  async initTusUpload() {\n    const res = await fetch(\"https://video.bunnycdn.com/tusupload\", {\n      method: \"POST\",\n      headers: {\n        \"Tus-Resumable\": \"1.0.0\",\n        \"Upload-Defer-Length\": \"1\",\n        AuthorizationSignature: this.signature,\n        AuthorizationExpire: String(this.expires),\n        LibraryId: String(this.libraryId),\n        VideoId: this.videoId,\n        \"Upload-Metadata\": `filetype ${btoa(this.container || \"video/webm\")},title ${btoa(\n          this.metadata.title,\n        )}`,\n      },\n    });\n\n    if (!res.ok) throw new Error(\"Failed to start TUS upload session\");\n    const location = res.headers.get(\"location\");\n    const resolved = location.startsWith(\"/\")\n      ? `https://video.bunnycdn.com${location}`\n      : location;\n    // Defense-in-depth: TUS Location header must stay on Bunny's host. Without\n    // this, a redirect to attacker.com would receive subsequent PATCHes\n    // carrying recording chunks plus the AuthorizationSignature header.\n    try {\n      const parsed = new URL(resolved);\n      if (parsed.host !== \"video.bunnycdn.com\") {\n        throw new Error(`Untrusted TUS location host: ${parsed.host}`);\n      }\n    } catch (err) {\n      throw new Error(`Invalid TUS location: ${err?.message || err}`);\n    }\n    this.uploadUrl = resolved;\n\n    // Persist BEFORE save-upload-meta: the local journal is the only recovery","sourceCodeStart":1196,"sourceCodeEnd":1232,"githubUrl":"https://github.com/alyssaxuu/screenity/blob/512606387b8d07dda5e63bb428bd063f0a2a3ed0/src/pages/CloudRecorder/bunnyTusUploader.js#L1196-L1232","documentation":"BunnyTusUploader creates a TUS upload session by POSTing to Bunny's video CDN creation endpoint and throws this when the HTTP response is not ok. It means Bunny rejected session creation (auth, quota, container/metadata, or network-level failure surfaced as a non-2xx), so no upload URL exists and no bytes can be sent.","triggerScenarios":"POST to the TUS creation endpoint returns non-ok status: invalid or expired AuthorizationSignature/token, missing or mis-encoded Upload-Metadata (e.g. non-ASCII title that btoa cannot encode), wrong collection/container id, or Bunny outage returning 4xx/5xx.","commonSituations":"Expired Bunny API key or signed upload token; recording started after auth expired; btoa throwing on Unicode metadata upstream; wrong library id in the creation URL; CORS/proxy stripping the Location header in dev environments.","solutions":["Log res.status and res body at the throw site to identify which 4xx/5xx Bunny returned","Refresh the Bunny auth signature/token before creating the session and retry once","Ensure metadata values are ASCII-safe before btoa (use a UTF-8 safe base64 encoder for the title)","Verify the library id and create endpoint URL match your Bunny account/region","Check Bunny status/incidents if failures are widespread"],"exampleFix":"// before\nif (!res.ok) throw new Error(\"Failed to start TUS upload session\");\n// after\nif (!res.ok) {\n  const body = await res.text().catch(() => \"\");\n  throw new Error(`Failed to start TUS upload session: HTTP ${res.status} ${body.slice(0, 200)}`);\n}","handlingStrategy":"try-catch","validationCode":"// before creating a session\nif (!bunnyToken) throw new Error(\"missing Bunny auth token\");\nconst res = await fetch(createUrl, { method: \"POST\", headers });\nif (!res.ok) console.error(\"TUS create failed\", res.status, await res.text());","typeGuard":null,"tryCatchPattern":"try {\n  await uploader.init();\n} catch (e) {\n  if (String(e.message).includes(\"Failed to start TUS upload session\")) {\n    await refreshBunnyToken();\n    await uploader.init(); // single retry after re-auth\n  } else throw e;\n}","preventionTips":["Refresh auth signatures proactively before long recordings","Keep metadata ASCII-safe before base64 encoding","Log HTTP status/body from the create request","Verify library id and endpoint URL per environment"],"tags":["network","http","upload","tus"],"backgroundTag":"tus-upload-create-failed","analyzedSha":"512606387b8d07dda5e63bb428bd063f0a2a3ed0","analyzedAt":"2026-09-02T20:59:09.419Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T02:17:09.455Z"}