You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
ctx.facets.clone() of a Think facet comes back empty in production (works locally) #2607
Drafted 2026-10-16 from Joke's production (account schematik, script joke).
Versions: agents 0.28.0, @cloudflare/think 0.20.2, ai 7.0.137, wrangler 4.147.0,
compatibility_date 2026-10-01, nodejs_compat. Object layout: JokeScope (root DO) → JokeSession (Think facet) → JokeWorker (Think facet, run with runAgentTool, detached).
(This is a runtime API; it may belong in workerd. Filed here because the facet is an agents sub-agent and
the archive path is the documented way to snapshot one without waking it.)
What happens
To archive a session without waking or stopping it, the root clones the facet and reads the clone:
constclone=`recovery-export:${crypto.randomUUID()}`;this.ctx.facets.clone(`${className}\0${sessionId}`,clone);try{constcopy=this.ctx.facets.get<RecoverySession>(clone,()=>({class: this.ctx.exports.RecoverySession}));returnawaitcopy.exportRecovery(key);// reads sqlite_master and every table of the copy}finally{this.ctx.facets.delete(clone);}
Locally (wrangler dev / miniflare, same compatibility date) the copy has the session's tables and rows. Hosted,
every copy has no tables.
Evidence
Nightly archive of 2026-10-15 (nightly/2026-10-15T06-23-37-000Z/ ): 201 conversations,
27 sessions exported, 27 of 27 with "tables": 0, "rows": 0, e.g. {"id":"c728855c-…","tables":0,"rows":0,"children":0} under a scope whose own export has 17 tables / 6 rows.
The live sessions are not empty (they serve the conversations in Desk).
Every nightly session export since we introduced the clone path (2026-10-11) was empty; one run also reset a
scope and its copy.
The source facet had been written by the same root shortly before in some cases; a facet started after the
root's own uncommitted writes is a known hosted reset for us.
Repro notes
Deploy a root DO with a Think facet; write messages into the facet.
In a later request on the root, ctx.facets.clone(sourceName, newName), then ctx.facets.get(newName, …)
with a class that lists sqlite_master.
Hosted: zero tables. Local: the source's tables.
await ctx.storage.sync() before the clone and again before starting the copy does not help: in the
2026-10-16 run all 404 conversations with sessions failed with "its copy had no tables".
An empty copy is what workerd also gives when the source name has no storage of its own, so hosted clone may be resolving the facet's storage differently from facets.get (e.g. by name without the
root's facet registry). Joke now archives each session from itself instead (Ovfe #803).
Questions
Is facets.clone expected to copy a facet that is currently live (open SQLite connection) in production?
Is there an ordering requirement (sync, or the source being idle) that the local runtime does not enforce?
Activity