{"record":{"id":"a6a462fc4a9401c6","repo":"alyssaxuu/screenity","slug":"missing-user-token-for-saving-upload-metadata","errorCode":null,"errorMessage":"Missing user token for saving upload metadata","messagePattern":"Missing user token for saving upload metadata","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/pages/CloudRecorder/bunnyTusUploader.js","lineNumber":933,"sourceCode":"          });\n        }\n      }\n\n      if (!this.videoId || !this.mediaId) {\n        const { authenticated, user } = await new Promise((resolve) => {\n          chrome.runtime.sendMessage({ type: \"check-auth-status\" }, resolve);\n        });\n\n        if (!authenticated) throw new Error(\"Not authenticated with Screenity\");\n\n        const { screenityToken } = await chrome.storage.local.get([\n          \"screenityToken\",\n        ]);\n\n        this.userToken = screenityToken;\n\n        if (!this.userToken) {\n          throw new Error(\"Missing user token for saving upload metadata\");\n        }\n\n        // Retry transient failures so a backend blip doesn't abort the\n        // recording. A 4xx is a real rejection, so don't retry it.\n        let res = null;\n        // This POST gates capture start (canBeginRecording waits on uploader\n        // init), so its server time is start latency.\n        const endCreatePost = perfSpan(\"Uploader POST /bunny/videos\", { type });\n        // Also kept on the instance: the perf timeline is capped and evicts\n        // the start phase on long recordings, so CloudRecorder mirrors these\n        // into the start-flow trace, which is a single uncapped object.\n        const createPostStartedAt = Date.now();\n        let postAttempts = 0;\n        for (let attempt = 0; attempt < 3; attempt += 1) {\n          if (attempt > 0) {\n            await new Promise((r) => setTimeout(r, 500 * attempt));\n          }\n          postAttempts = attempt + 1;","sourceCodeStart":915,"sourceCodeEnd":951,"githubUrl":"https://github.com/alyssaxuu/screenity/blob/512606387b8d07dda5e63bb428bd063f0a2a3ed0/src/pages/CloudRecorder/bunnyTusUploader.js#L915-L951","documentation":"initialize() reports the user as authenticated via check-auth-status, but the actual bearer token used to authorize the POST /bunny/videos call is read from chrome.storage.local as `screenityToken`. If that key is absent or empty, there is no credential to attach to the request, so initialize() throws before any network call — it can't save upload metadata or create the Bunny video without it. This catches the mismatch where the background says 'authenticated' but the page-local token copy is missing.","triggerScenarios":"initialize() took the create-new-video path (no reuse/journal/video-map hit), check-auth-status returned authenticated:true, but chrome.storage.local.get('screenityToken') resolved undefined/empty — e.g. token was cleared, storage partition mismatch, or the background stores auth elsewhere.","commonSituations":"chrome.storage.local was cleared (extension update, chrome.identity sync, user cleared site data) while background in-memory auth state still says signed in; a version change renamed the storage key; content script running in a context where storage.local isn't shared with where the token was written.","solutions":["Re-run the sign-in flow so screenityToken is (re)written to chrome.storage.local, then retry initialize().","Check chrome.storage.local.get(null) in the same context to confirm the key name is exactly 'screenityToken' and hasn't been renamed or nested.","Fix the background so it persists the token to storage.local after check-auth-status succeeds (or have check-auth-status return the token directly) so page and background agree.","Clear the extension's auth state and sign in again if a stale 'authenticated' flag is masking missing storage data."],"exampleFix":"// before\nconst { screenityToken } = await chrome.storage.local.get([\"screenityToken\"]);\n// after\nlet { screenityToken } = await chrome.storage.local.get([\"screenityToken\"]);\nif (!screenityToken) {\n  ({ screenityToken } = await chrome.runtime.sendMessage({ type: \"get-auth-token\" }));\n  if (screenityToken) await chrome.storage.local.set({ screenityToken });\n}","handlingStrategy":"validation","validationCode":"const { screenityToken } = await chrome.storage.local.get(['screenityToken']);\nif (!screenityToken) {\n  await reauthenticate(); // rewrite screenityToken to storage.local before init\n}","typeGuard":"function hasUserToken(state) {\n  return typeof state?.screenityToken === 'string' && state.screenityToken.length > 0;\n}","tryCatchPattern":"try {\n  await uploader.initialize({ projectId, sceneId, type });\n} catch (err) {\n  if (String(err?.message).includes('Missing user token')) {\n    await reauthenticateScreenity(); // restores screenityToken in storage\n    await uploader.initialize({ projectId, sceneId, type });\n  } else throw err;\n}","preventionTips":["After any successful sign-in, verify screenityToken exists in chrome.storage.local before enabling recording.","Keep the storage key name in a shared constant to avoid rename drift between background and page.","Have check-auth-status also confirm the token is persisted, not just session state.","Audit storage-clearing code paths (updates, logout) to ensure they re-populate the token on next sign-in."],"tags":["authentication","token","chrome-storage","extension"],"backgroundTag":"missing-auth-token","analyzedSha":"512606387b8d07dda5e63bb428bd063f0a2a3ed0","analyzedAt":"2026-09-02T20:59:09.419Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T02:17:09.455Z"}