Fix: restore __ps_name storage hydrate for Agents facets (0.5.1)#381
Merged
threepointone merged 3 commits intomainfrom Apr 24, 2026
Merged
Fix: restore __ps_name storage hydrate for Agents facets (0.5.1)#381threepointone merged 3 commits intomainfrom
threepointone merged 3 commits intomainfrom
Conversation
0.5.0 moved the legacy __ps_name storage hydrate into `alarm()` only, which broke Cloudflare Agents facets and any other framework that writes __ps_name directly before calling __unsafe_ensureInitialized(). Facet DOs are spawned via `ctx.facets.get(...)` rather than `idFromName()`, so their `ctx.id.name` is `undefined`. They were relying on PartyServer's `#ensureInitialized()` calling `#hydrateNameFromStorage()` to populate `this.name` before `onStart()`. This PR: - Moves the hydrate from `alarm()` into `#ensureInitialized()`, gated on `!ctx.id.name && !#_name` so the happy path (normal idFromName DOs) still pays zero storage reads. - Simplifies `Server.fetch()` to delegate the hydrate to `#ensureInitialized()`. The x-partykit-room header remains as a last-resort fallback when neither ctx.id.name nor storage has a value. - Simplifies `Server.alarm()` — no separate hydrate call needed. - Softens `@deprecated` on `setName()` to clarify it's still the right primitive for framework bootstrap of non-idFromName DOs (e.g. facets). - Adds FacetLikeBootstrapServer + tests covering the fetch-time and RPC-time hydrate paths. Made-with: Cursor
🦋 Changeset detectedLatest commit: ed252d6 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
hono-party
partyfn
partyserver
partysocket
partysub
partysync
partytracks
partywhen
y-partyserver
commit: |
The first pass of PR #381 ran #ensureInitialized() BEFORE applying the x-partykit-room header fallback. For DOs with ctx.id.name undefined and no __ps_name storage record, that meant onStart() ran against an unset #_name and threw if it read this.name. Restore the 0.5.0 ordering: pre-populate #_name from the header before ensureInitialized runs, so onStart() sees the correct name regardless of which source provided it. The resolvability throw is now a final check after ensureInitialized. Resolution priority is now: ctx.id.name > x-partykit-room header > legacy __ps_name storage. Storage only wins when neither of the other two supplies the name. Adds HeaderOnlyOnStartServer + a regression test covering the newUniqueId + x-partykit-room + onStart-reads-name path. Made-with: Cursor
Merged
This was referenced Apr 24, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
0.5.0 moved the legacy
__ps_namestorage hydrate out of#ensureInitialized()and intoalarm()only, with the reasoning being thatctx.id.namecovers every other entry point for normal DOs addressed viaidFromName()/getByName().That reasoning missed Cloudflare Agents facets. Facet DOs are spawned via
ctx.facets.get(facetKey)— notidFromName()— so theirctx.id.nameisundefined. Agents bootstraps the facet's name by writing__ps_nameto storage and callingthis.__unsafe_ensureInitialized(), relying on PartyServer to read the storage record back:With 0.5.0 this breaks:
#_namestaysundefined,onStart()throws on anythis.nameread, and subsequentfetcher.fetch(...)calls from_cf_forwardToFacetalso throw.Fix
Move the legacy hydrate from
alarm()into#ensureInitialized(), still gated on!ctx.id.name && !#_name— the happy path (normalidFromName/getByNameDOs) still pays zero storage reads sincectx.id.nameis set and the gate short-circuits.Name resolution priority:
ctx.id.name>x-partykit-roomheader > legacy__ps_namestorage record.Changes
#ensureInitialized()does the conditional storage hydrate for any entry point that needs it (fetch, alarm, websocket handlers,__unsafe_ensureInitializedvia RPC).Server.fetch()pre-populates#_namefrom thex-partykit-roomheader before calling#ensureInitialized(), soonStart()can readthis.nameregardless of which source provided it. A final resolvability check after#ensureInitialized()throws with a useful error when no source could supply the name.Server.alarm()is simplified — no duplicate hydrate call needed.setName()@deprecateddocblock is softened. It's still the right primitive for framework bootstrap of non-idFromNameDOs (e.g. Agents facets); deprecating it outright was misleading.Test plan
FacetLikeBootstrapServerDO + 2 tests covering the facet pattern: storage-write-then-ensureInitialized, plus cold-wake fetch recoveryHeaderOnlyOnStartServerDO + test covering thenewUniqueId+x-partykit-roomheader +onStart()-reads-this.namepath (regression guard for the ordering bug in the first pass of this PR)partyserverpeer range to>=0.5.1once this shipsPerformance
Zero change for normal DOs. The conditional gate (
!ctx.id.name && !#_name) short-circuits before any storage read whenever the name is already known — which is the happy path for everyidFromName/getByNameuser. Only DOs that truly need the fallback (facets, pre-2026-03-15 alarms,newUniqueId+ header callers where the name doesn't match storage) pay for one storage read on first entry.