Skip to content

Setting any static options on an Agent subclass silently disables hibernation #2012

Description

@urjitc

ai slop but still true:

Describe the bug

Setting any static options on an Agent subclass silently disables hibernation.

Agent.options defaults hibernate: true, and the docs state that "Child classes can override specific options without spreading" (dist/index.js:285, and in the shipped .d.ts:3050). But a subclass static replaces the inherited object rather than merging, and partyserver selects the connection manager from the raw static with no defaulting:

// partyserver@0.5.9 dist/index.js:546,549
static options = { hibernate: false };
#ParentClass = Object.getPrototypeOf(this).constructor;
#connectionManager = this.#ParentClass.options.hibernate
  ? new HibernatingConnectionManager(this.ctx)
  : new InMemoryConnectionManager();

So static options = { sendIdentityOnConnect: false } leaves hibernate === undefinedInMemoryConnectionManager. Meanwhile _resolvedOptions (agents dist/index.js:461) does apply the default, so the SDK still reports hibernate: true. Two layers read the same static; only the one that doesn't pick the connection manager defaults it.

This is especially easy to hit because the SDK itself recommends the offending line. agents dist/index.js:957 warns:

[Agent] ${ctor.name}: sending instance name "${this.name}" to clients via sendIdentityOnConnect ... If this name is sensitive, add static options = { sendIdentityOnConnect: false } to opt out.

Following that advice, to fix a stated privacy concern, silently turns off hibernation.

To Reproduce

Steps to reproduce the behavior:

  1. Define an Agent subclass that overrides a single option without spreading, exactly as the docs describe:
    class MyAgent extends Agent {
      static options = { sendIdentityOnConnect: false };
    }
  2. Connect a WebSocket to it.
  3. Inspect MyAgent.options.hibernate — it is undefined, so partyserver constructs an InMemoryConnectionManager and accepts via connection.accept() instead of ctx.acceptWebSocket(...).
  4. Note that _resolvedOptions.hibernate still reports true, so nothing surfaces the mismatch.
  5. Add hibernate: true back into the same object and the HibernatingConnectionManager is selected again.

Expected behavior

Overriding one option should leave the others at their documented defaults, so hibernate stays true unless explicitly set to false. At minimum, _resolvedOptions and the connection-manager selection should agree.

Screenshots

N/A.

Version:

agents@0.20.1 with partyserver@0.5.9. Also present in agents@0.19.0 / partyserver@0.5.8 — the relevant partyserver lines are unchanged between the two.

Additional context

The impact is silent and billing-related rather than functional: HibernatingConnectionManager.accept calls ctx.acceptWebSocket(...), while InMemoryConnectionManager.accept calls connection.accept(), so the Durable Object stays resident for the life of every WebSocket and bills wall-clock duration while idle. There is no error, and the SDK's own resolved options report the opposite.

Suggested fix: merge subclass statics into the defaults at class-definition time, so the raw options object partyserver reads is always complete — which is what the docs already promise. A dev-mode warning when options is set without hibernate would also catch it cheaply.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions