Observation-class finding, noticed while implementing #7724 (PR #7879). Not a defect anyone hits in a normal deployment — filing it because the failure mode is the silent kind.
What
ObjectQL.transaction() (ADR-0034) publishes its driver handle into an AsyncLocalStorage-backed ambient store (txStore), and buildDriverOptions reads it as the safety net so internal queries and nested writes join the open transaction without being threaded explicitly:
const tx = execCtx?.transaction !== undefined
? execCtx.transaction
: this.txStore.getStore()?.transaction;
packages/plugins/plugin-auth/src/auth-manager.ts already documents, and works around, the fact that this does not hold everywhere:
WebContainer reports itself as Node.js but runs inside a browser. Several Node APIs are polyfilled with significant behavioural differences — most notably node:async_hooks.AsyncLocalStorage, whose run() does NOT propagate the store across await boundaries the way Node's native implementation does.
That file installs WebContainerRequestStateAsyncLocalStorage to repair better-auth's request-state ALS. txStore has no equivalent.
Why it is worth recording
If AsyncLocalStorage.run() does not survive await in that runtime, then inside a transaction() callback every ambient lookup returns undefined, so the writes execute outside the transaction — and then commit or roll back an empty transaction. The caller is told the unit of work rolled back; the rows are still there. That is the shape AGENTS.md singles out: the system looks normal while something it claims about persistence is not true. Unlike the missing-beginTransaction case, there is no warning — warnTransactionUnsupported does not fire, because the driver does support transactions.
Every transaction() caller inherits this equally (CRM convertLead, publishPackageDrafts, batchData's atomic gate, and now #7724's subject-erasure wrapper). It is not specific to any of them.
Not verified, deliberately
I did not reproduce this in a WebContainer — I have no such runtime here, and I did not want to assert a measurement I had not made. What is verified is only that (a) the repo states this ALS caveat in its own words, and (b) txStore has no polyfill while better-auth's request-state store does. Whether the gap actually bites depends on WebContainer's current polyfill, which may have improved.
So the first step is measurement, not a fix: run one transaction() whose callback writes and then throws, under WebContainer, and check whether the row survived.
No pm:queue
Dormant for real deployments — WebContainer is a StackBlitz dev/demo runtime. Filed unassigned for triage rather than sat on, per the filing-discipline rule that severity judged at filing time is unreliable in both directions.
Observation-class finding, noticed while implementing #7724 (PR #7879). Not a defect anyone hits in a normal deployment — filing it because the failure mode is the silent kind.
What
ObjectQL.transaction()(ADR-0034) publishes its driver handle into anAsyncLocalStorage-backed ambient store (txStore), andbuildDriverOptionsreads it as the safety net so internal queries and nested writes join the open transaction without being threaded explicitly:packages/plugins/plugin-auth/src/auth-manager.tsalready documents, and works around, the fact that this does not hold everywhere:That file installs
WebContainerRequestStateAsyncLocalStorageto repair better-auth's request-state ALS.txStorehas no equivalent.Why it is worth recording
If
AsyncLocalStorage.run()does not surviveawaitin that runtime, then inside atransaction()callback every ambient lookup returnsundefined, so the writes execute outside the transaction — and then commit or roll back an empty transaction. The caller is told the unit of work rolled back; the rows are still there. That is the shape AGENTS.md singles out: the system looks normal while something it claims about persistence is not true. Unlike the missing-beginTransactioncase, there is no warning —warnTransactionUnsupporteddoes not fire, because the driver does support transactions.Every
transaction()caller inherits this equally (CRMconvertLead,publishPackageDrafts,batchData's atomic gate, and now #7724's subject-erasure wrapper). It is not specific to any of them.Not verified, deliberately
I did not reproduce this in a WebContainer — I have no such runtime here, and I did not want to assert a measurement I had not made. What is verified is only that (a) the repo states this ALS caveat in its own words, and (b)
txStorehas no polyfill while better-auth's request-state store does. Whether the gap actually bites depends on WebContainer's current polyfill, which may have improved.So the first step is measurement, not a fix: run one
transaction()whose callback writes and then throws, under WebContainer, and check whether the row survived.No
pm:queueDormant for real deployments — WebContainer is a StackBlitz dev/demo runtime. Filed unassigned for triage rather than sat on, per the filing-discipline rule that severity judged at filing time is unreliable in both directions.