Skip to content

Comments

fix: persist Server.name to durable storage for alarm/cold-start hydration#337

Merged
threepointone merged 1 commit intomainfrom
set-name-fix
Feb 22, 2026
Merged

fix: persist Server.name to durable storage for alarm/cold-start hydration#337
threepointone merged 1 commit intomainfrom
set-name-fix

Conversation

@threepointone
Copy link
Collaborator

Summary

this.name throws when accessed inside onAlarm() or scheduled callbacks because alarms are the only Durable Object entry point that never receives an HTTP request — and partyserver's name was only set from the x-partykit-room request header.

This PR persists the name to ctx.storage.kv (synchronous DO storage) on setName() and hydrates it lazily in the name getter. This makes this.name work in every context — alarms, WebSocket hibernation wake-ups, direct stub.fetch() without the header, and any future entry point.

Fixes cloudflare/agents#933. Supersedes #331 with a more comprehensive approach.

What changed

Core fix: name persistence (packages/partyserver/src/index.ts)

  • setName() now persists the name to ctx.storage.kv.put("__ps_name", name) synchronously alongside setting #_name.
  • name getter hydrates from ctx.storage.kv.get("__ps_name") before throwing, so the name is available on any cold start where it was previously persisted.
  • Removed #_longErrorAboutNameThrown — the dedup flag is no longer needed since the getter now falls back to storage before erroring.

Cleanup: consolidated initialization

  • Renamed #initialize()#ensureInitialized() with an early if (this.#status === "started") return guard, making it idempotent. Eliminated 4 separate if (this.#status !== "started") checks across fetch(), setName(), alarm(), and the WS handlers.

Cleanup: decoupled name from WS handlers

  • webSocketMessage/Close/Error no longer call setName(connection.server). They call #ensureInitialized() instead. The name is handled by the getter's storage hydration, not by reading it from the per-connection WebSocket attachment. This resolves the // TODO: this shouldn't be async comments on all three handlers.

Cleanup: fetch() storage fallback

  • fetch() now tries storage before requiring the header. On cold start, if the name was previously persisted, the x-partykit-room header is no longer required. The header is only needed on first-ever contact with a DO. This means direct stub.fetch() calls work for previously-initialized DOs.
  • Removed stale comments referencing workerd#2240 as "temporary".

Dependency range widening

  • Widened partyserver dependency ranges in hono-party, partysub, partysync, y-partyserver, and partywhen from ^0.2.0 (which means >=0.2.0 <0.3.0 in 0.x semver) to >=0.2.0 <1.0.0 so a minor bump doesn't cascade into major bumps on all dependents.
  • Added onlyUpdatePeerDependentsWhenOutOfRange to changeset config to prevent unnecessary major cascades for 0.x packages.

How it works

┌─────────────────────────────────────────────────────────┐
│ setName("my-room")                                      │
│   1. this.#_name = "my-room"                            │
│   2. ctx.storage.kv.put("__ps_name", "my-room")  [sync] │
│   3. #ensureInitialized() → onStart()                   │
└─────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────┐
│ get name() — on any cold start                          │
│   1. #_name is undefined                                │
│   2. ctx.storage.kv.get("__ps_name")              [sync] │
│   3. Found → cache in #_name, return                    │
│   4. Not found → throw (name was never set)             │
└─────────────────────────────────────────────────────────┘

Comparison with #331

#331 This PR
Storage API await ctx.storage.put/get (async) ctx.storage.kv.put/get (sync)
Where name hydrates Only in alarm() In the name getter — all entry points
fetch() header Still always required Falls back to storage for returning DOs
WS handlers Unchanged (setName(connection.server)) Simplified to #ensureInitialized()
Init guards 4 separate if (#status) checks Single idempotent #ensureInitialized()

Test plan

8 new tests added (40 total, up from 32):

  • persists name to storage when setName is called — basic write path
  • this.name is available inside onAlarm after normal setup — warm alarm
  • hydrates name from storage on cold alarm wake (bypassing setName) — seeds storage directly without calling setName, proving the getter's KV read is the recovery mechanism
  • this.name is available inside onStart during alarm wake — critical for Agent's MCP restoration
  • fetch() hydrates name from storage without requiring the header — direct stub.fetch works for returning DOs
  • setName is idempotent for the same value — no throw on repeated calls
  • throws when name was never set and is not in storage — brand-new DOs still error correctly
  • getServerByName persists the name for future access — name set via routing is available on subsequent headerless fetches

All 40 tests pass.

Made with Cursor

Persist the Durable Object server name so it survives cold starts and is available inside onStart/onAlarm and scheduled callbacks. Introduces a NAME_STORAGE_KEY constant and has setName write the name to storage; fetch(), alarms and ws handlers hydrate the name from storage via a new #ensureInitialized() helper. Adds tests and two test DOs (AlarmNameServer, NoNameServer) plus wrangler test config updates; updates package peer/dev dependency ranges for partyserver and bumps partysocket. Adds a changeset entry and enables an experimental changesets option.
@changeset-bot
Copy link

changeset-bot bot commented Feb 22, 2026

🦋 Changeset detected

Latest commit: c7eda2e

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
partyserver Minor

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

@pkg-pr-new
Copy link

pkg-pr-new bot commented Feb 22, 2026

Open in StackBlitz

hono-party

npm i https://pkg.pr.new/cloudflare/partykit/hono-party@337

partyfn

npm i https://pkg.pr.new/cloudflare/partykit/partyfn@337

partyserver

npm i https://pkg.pr.new/cloudflare/partykit/partyserver@337

partysocket

npm i https://pkg.pr.new/cloudflare/partykit/partysocket@337

partysub

npm i https://pkg.pr.new/cloudflare/partykit/partysub@337

partysync

npm i https://pkg.pr.new/cloudflare/partykit/partysync@337

partytracks

npm i https://pkg.pr.new/cloudflare/partykit/partytracks@337

partywhen

npm i https://pkg.pr.new/cloudflare/partykit/partywhen@337

y-partyserver

npm i https://pkg.pr.new/cloudflare/partykit/y-partyserver@337

commit: c7eda2e

@threepointone threepointone merged commit 67685b9 into main Feb 22, 2026
6 checks passed
@threepointone threepointone deleted the set-name-fix branch February 22, 2026 16:31
@github-actions github-actions bot mentioned this pull request Feb 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

workflows: this.name is not set when trying to run inside an alarm

1 participant