Is there an existing issue for this?
How do you use Sentry?
Sentry Saas (sentry.io)
Which SDK are you using?
@sentry/vue
SDK Version
10.46.0
Framework Version
3.5.31
Link to Sentry event
No response
Reproduction Example/SDK Setup
import * as Sentry from "@sentry/vue";
// Module-level — runs before Sentry.init()
const earlyRef = window.localStorage;
Sentry.init({
app,
dsn: "...",
integrations: [Sentry.replayIntegration()],
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1.0,
});
Steps to Reproduce
Capture a reference to window.localStorage at module load — before Sentry.init() runs. This happens naturally when a persistence library accepts a storage option configured at module scope (we hit it via pinia-plugin-persistedstate, which captures the supplied storage once at store definition).
Call Sentry.init() with replayIntegration() enabled.
Later (e.g. on user interaction), call .setItem(key, value) through the captured reference:
earlyRef.setItem("demo", "hello");
Inspect both storage backends:
localStorage.getItem("demo"); // null
sessionStorage.getItem("demo"); // "hello"
Expected Result
The write reaches localStorage. Calling .setItem on a localStorage reference should mutate localStorage regardless of when that reference was acquired relative to Sentry.init().
Actual Result
The write is silently redirected into sessionStorage. No error is thrown, no console warning, no breadcrumb. The key never appears in localStorage, and data intended to persist disappears on tab close.
Inspecting Storage.prototype.setItem after init shows the patched wrapper (minified):
function () {
let t = null, a = false;
return this === i.localStorage.proxy
? (a = true, t = i.localStorage.original)
: (a = false, t = i.sessionStorage.original),
e(arguments, a),
o.apply(t, arguments);
}
The else branch writes to sessionStorage.original for any receiver that is not strictly equal to i.localStorage.proxy — including a raw localStorage reference captured before init. A receiver check via instanceof Storage plus comparison against both localStorage.original and sessionStorage.original, or a WeakMap lookup, would route correctly.
Additional Context
Confirmed in production with replaysSessionSampleRate: 0.1 and replaysOnErrorSampleRate: 1.0 (instrumentation runs regardless of sampling).
The bug is invisible without explicit verification — applications appear to "lose" persisted state on reload while the writes are actually landing in sessionStorage.
Symmetric risk exists for code holding a sessionStorage reference taken before init, although we have not verified that direction.
Workaround (in user code) is to late-bind through window.localStorage so each call resolves the current (proxied) instance:
const lateBoundLocalStorage = {
getItem: (k: string) => window.localStorage.getItem(k),
setItem: (k: string, v: string) => window.localStorage.setItem(k, v),
removeItem: (k: string) => window.localStorage.removeItem(k),
};
Likely affects any persistence library that takes a Storage option at module scope (e.g. pinia-plugin-persistedstate, redux-persist, zustand/middleware/persist with custom storage).
Priority
React with 👍 to help prioritize this issue. Please use comments to provide useful context, avoiding +1 or me too, to help us triage it.
Is there an existing issue for this?
How do you use Sentry?
Sentry Saas (sentry.io)
Which SDK are you using?
@sentry/vue
SDK Version
10.46.0
Framework Version
3.5.31
Link to Sentry event
No response
Reproduction Example/SDK Setup
Steps to Reproduce
Capture a reference to
window.localStorageat module load — beforeSentry.init()runs. This happens naturally when a persistence library accepts a storage option configured at module scope (we hit it via pinia-plugin-persistedstate, which captures the supplied storage once at store definition).Call
Sentry.init()withreplayIntegration()enabled.Later (e.g. on user interaction), call
.setItem(key, value)through the captured reference:earlyRef.setItem("demo", "hello");Inspect both storage backends:
localStorage.getItem("demo"); // nullsessionStorage.getItem("demo"); // "hello"Expected Result
The write reaches localStorage. Calling .setItem on a localStorage reference should mutate localStorage regardless of when that reference was acquired relative to Sentry.init().
Actual Result
The write is silently redirected into sessionStorage. No error is thrown, no console warning, no breadcrumb. The key never appears in localStorage, and data intended to persist disappears on tab close.
Inspecting
Storage.prototype.setItemafter init shows the patched wrapper (minified):The else branch writes to sessionStorage.original for any receiver that is not strictly equal to i.localStorage.proxy — including a raw localStorage reference captured before init. A receiver check via instanceof Storage plus comparison against both localStorage.original and sessionStorage.original, or a WeakMap lookup, would route correctly.
Additional Context
Confirmed in production with replaysSessionSampleRate: 0.1 and replaysOnErrorSampleRate: 1.0 (instrumentation runs regardless of sampling).
The bug is invisible without explicit verification — applications appear to "lose" persisted state on reload while the writes are actually landing in sessionStorage.
Symmetric risk exists for code holding a sessionStorage reference taken before init, although we have not verified that direction.
Workaround (in user code) is to late-bind through window.localStorage so each call resolves the current (proxied) instance:
Likely affects any persistence library that takes a Storage option at module scope (e.g. pinia-plugin-persistedstate, redux-persist, zustand/middleware/persist with custom storage).
Priority
React with 👍 to help prioritize this issue. Please use comments to provide useful context, avoiding
+1orme too, to help us triage it.