-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Description
Problem
Since 10.39.0, instrumentDurableObjectWithSentry breaks any DurableObject that accesses ctx.storage.sql (SQLite storage API). Both production workerd and miniflare are affected.
Error:
SqlError: SQL query failed: Illegal invocation: function called with incorrect `this` reference.
10.38.0 works. 10.39.0+ does not.
Root Cause
instrumentDurableObjectStorage (in packages/cloudflare/src/instrumentations/instrumentDurableObjectStorage.ts) wraps DurableObjectStorage in a Proxy whose get trap uses Reflect.get(target, prop, receiver) where receiver is the proxy:
get(target, prop, receiver) {
const original = Reflect.get(target, prop, receiver); // receiver = proxyWhen accessing storage.sql, the native getter on DurableObjectStorage validates this via internal slots (brand check). Passing the proxy as receiver means the getter executes with this = proxy instead of the real native storage object, failing the brand check.
Non-function properties like sql (a getter returning SqlStorage) hit the typeof original !== 'function' early return and are returned directly — but by then the getter has already been called with the wrong this.
Why KV methods (get/put/delete/list) are unaffected: they are functions, so the proxy explicitly .bind(target)s them or wraps them with Reflect.apply(original, target, args). The this is correct for functions, but not for getters.
Fix
One-line change — use target as the receiver:
- get(target, prop, receiver) {
- const original = Reflect.get(target, prop, receiver);
+ get(target, prop, _receiver) {
+ const original = Reflect.get(target, prop, target);This ensures native getters execute with the real storage object as this.
Minimal Reproduction
import { DurableObject } from "cloudflare:workers";
import { instrumentDurableObjectWithSentry } from "@sentry/cloudflare";
class CounterBase extends DurableObject {
constructor(ctx: DurableObjectState, env: Env) {
super(ctx, env);
// Throws: SqlError: Illegal invocation
this.ctx.storage.sql.exec(
"CREATE TABLE IF NOT EXISTS counts (name TEXT PRIMARY KEY, value INTEGER DEFAULT 0)"
);
}
}
export const Counter = instrumentDurableObjectWithSentry(
(env) => ({ dsn: env.SENTRY_DSN }),
CounterBase,
);Related
- Cloudflare Durable Object instrumentDurableObjectWithSentry throwing errors #17094 — similar
instrumentContextissue, but about sync methods being wrapped async. Fixed in v9.42.1/v10.0.0 via fix(cloudflare): Avoid turning DurableObject sync methods into async #17184. TheReflect.getreceiver issue is a different root cause not addressed by that fix.
Environment
@sentry/cloudflare10.39.0+ (any version withinstrumentDurableObjectStorage)- Both production workerd and miniflare
- Any DurableObject using SQLite storage API (
ctx.storage.sql)
Metadata
Metadata
Assignees
Labels
Projects
Status