alyssaxuu/screenity · error · Error

Missing user token for saving upload metadata

Error message

Missing user token for saving upload metadata

What it means

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.

Source

Thrown at src/pages/CloudRecorder/bunnyTusUploader.js:933

          });
        }
      }

      if (!this.videoId || !this.mediaId) {
        const { authenticated, user } = await new Promise((resolve) => {
          chrome.runtime.sendMessage({ type: "check-auth-status" }, resolve);
        });

        if (!authenticated) throw new Error("Not authenticated with Screenity");

        const { screenityToken } = await chrome.storage.local.get([
          "screenityToken",
        ]);

        this.userToken = screenityToken;

        if (!this.userToken) {
          throw new Error("Missing user token for saving upload metadata");
        }

        // Retry transient failures so a backend blip doesn't abort the
        // recording. A 4xx is a real rejection, so don't retry it.
        let res = null;
        // This POST gates capture start (canBeginRecording waits on uploader
        // init), so its server time is start latency.
        const endCreatePost = perfSpan("Uploader POST /bunny/videos", { type });
        // Also kept on the instance: the perf timeline is capped and evicts
        // the start phase on long recordings, so CloudRecorder mirrors these
        // into the start-flow trace, which is a single uncapped object.
        const createPostStartedAt = Date.now();
        let postAttempts = 0;
        for (let attempt = 0; attempt < 3; attempt += 1) {
          if (attempt > 0) {
            await new Promise((r) => setTimeout(r, 500 * attempt));
          }
          postAttempts = attempt + 1;

View on GitHub (pinned to 512606387b)

Solutions

  1. Re-run the sign-in flow so screenityToken is (re)written to chrome.storage.local, then retry initialize().
  2. 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.
  3. 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.
  4. Clear the extension's auth state and sign in again if a stale 'authenticated' flag is masking missing storage data.

Example fix

// before
const { screenityToken } = await chrome.storage.local.get(["screenityToken"]);
// after
let { screenityToken } = await chrome.storage.local.get(["screenityToken"]);
if (!screenityToken) {
  ({ screenityToken } = await chrome.runtime.sendMessage({ type: "get-auth-token" }));
  if (screenityToken) await chrome.storage.local.set({ screenityToken });
}
Defensive patterns

Strategy: validation

Validate before calling

const { screenityToken } = await chrome.storage.local.get(['screenityToken']);
if (!screenityToken) {
  await reauthenticate(); // rewrite screenityToken to storage.local before init
}

Type guard

function hasUserToken(state) {
  return typeof state?.screenityToken === 'string' && state.screenityToken.length > 0;
}

Try / catch

try {
  await uploader.initialize({ projectId, sceneId, type });
} catch (err) {
  if (String(err?.message).includes('Missing user token')) {
    await reauthenticateScreenity(); // restores screenityToken in storage
    await uploader.initialize({ projectId, sceneId, type });
  } else throw err;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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