Repository navigation
Description
Restart-when: one write-then-throw ObjectQL.transaction() is run under WebContainer on the packages/drivers/driver-sqlite-wasm STACKBLITZ.md demo host — promote if the row survives (measured silent-persistence lie), close if ALS behaves; or WebContainer becomes a supported production posture
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, whoserun()does NOT propagate the store acrossawaitboundaries 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.
Generated by Claude Code
Activity
hotlong commentedon Aug 12, 2026
Triage: routed
domain:engine-core(packages/objectql—txStore/buildDriverOptions); hold asfindingstands. The filer's honesty is the grade: nothing is measured broken (deliberately unverified — no WebContainer runtime at hand), the exposure is dev/demo-runtime-only today, and the first step is a measurement (onetransaction()callback that writes then throws, under WebContainer; did the row survive?), not a fix. The silent-empty-transaction failure shape is nasty enough that the card earns its place in the stock rather than a close.Restart conditions: (a) anyone running the WebContainer measurement (the plugin-auth
WebContainerRequestStateAsyncLocalStorageworkaround is the repair template if it bites); (b) WebContainer becoming a supported production posture; (c) any report of phantom-rollback behavior in StackBlitz demos.Generated by Claude Code
hotlong commentedon Aug 12, 2026
Finding-grading round: hold confirmed, with a sharper trigger than the card carried: run one
transaction()that writes-then-throws under WebContainer — the shippeddriver-sqlite-wasm/STACKBLITZ.mddemo is a ready host — and attach the result to the next StackBlitz/dogfood pass. Promote on a measured silent-persistence lie; close if ALS behaves.engine.ts:2639/engine-transaction-same-origin.test.ts:563for coverage of this — that caveat is the ScopedContext/setImmediategap (#5351), a different mechanism.Generated by Claude Code
hotlong commentedon Aug 13, 2026
State-machine migration, maintainer-authorized (2026-08-13, live triage session: 「我授权你先迁标签」; semantics on #8449): graded hold with a sharp trigger (one write-then-throw
transaction()under WebContainer on the STACKBLITZ demo host; promote on a measured silent-persistence lie, close if ALS behaves) —finding→pm:on-hold. Verdict, trigger, domain unchanged.Generated by Claude Code
objectstack-fleet commentedon Sep 23, 2026
关
not_planned—— 维护者逐张复核 on-hold 卡时同意关闭;只影响在线演示,且从未实测分诊席(
session_01Tw7jnJinGHvoGSi8aFkhPJ),2026-09-23T08:03Z。维护者 2026-09-23 在分诊会话里逐张复核pm:on-hold卡,对第二组的回复原文:「8006 保留,其他同意」。本卡在那一组里的建议是关闭,维护者同意了。呈给维护者的事实
ObjectQL.transaction()通过AsyncLocalStorage(txStore)把事务句柄放进环境上下文。在 WebContainer(StackBlitz,浏览器里跑的 Node)中,它的 polyfill 可能在await之后丢掉上下文 ⇒ 一次「先写后抛错」的事务,可能留下半截数据,却报告已经回滚。本地main上:txStore仍没有 WebContainer 专用的 polyfill(packages/objectql/src/engine.ts),而plugin-auth给它自己的存储装了一个。packages/drivers/driver-sqlite-wasm/STACKBLITZ.md),而 agent 的执行环境跑不了 WebContainer。挂起 6 周,没有人跑过。重开条件
有人在 StackBlitz demo 里跑一次「先写后抛错」的
transaction(),并且那一行留下来了(实测到事务谎报回滚);或者 WebContainer 成了受支持的生产形态;或者有人报告在 StackBlitz 演示里见到了「回滚后数据还在」。修复的模板现成:plugin-auth的WebContainerRequestStateAsyncLocalStorage。关闭理由:
not_planned,同时摘掉pm:on-hold。Generated by Claude Code