Agent.destroy() leaves the Durable Object behind in production: deleted deadman alarm fires after abort · Issue #2605 · cloudflare/agents · GitHub
Skip to content

Agent.destroy() leaves the Durable Object behind in production: deleted deadman alarm fires after abort #2605

Open

Description

@ColinMcCullough

Summary

Agent.destroy() does not remove the Durable Object in production. About 30s after destroy() runs from a scheduled callback, the object wakes up, and the Agent constructor recreates the SDK's tables. Each destroyed agent is left behind with about 15 empty tables and never goes away.

This looks like the same symptom as #616, which was closed after #653.

Cause

destroy() ends with deleteAlarm() (through lifecycle.disableAlarms()), then deleteAll(), then a deferred ctx.abort(reason, { retryAlarm: false }). In production, an alarm deleted just before ctx.abort() still fires. The alarm here is the 30s deadman alarm (DEADMAN_ALARM_DELAY_MS) that _driveDueJobs sets before running due jobs.

We reproduced it without the SDK, using only those platform calls. With the abort, 5 of 6 objects woke again. Without it, 0 of 4 did. Runtime report: cloudflare/workerd#7788

Reproduction (SDK)

  • agents@0.27.0 and @cloudflare/ai-chat@0.12.1. 0.28.1 has the same destroy().
  • compatibility_date is 2026-03-02, with SQLite-backed Durable Objects.
  • An AIChatAgent subclass schedules a callback (this.schedule(seconds, 'expire')). The callback calls await this.destroy().
Point Tables getAlarm()
Right after destroy() 0 null
~30s later, constructor runs (12 of 12 objects, within 56ms of the deadman time) 0, then 15 after the constructor's migrations null
Woken manually 5 to 26 minutes later 15 null

Calling deleteAlarm() and storage.sync() before destroy() did not help, and neither did _cf_scheduleDestroy(). Local wrangler dev does not reproduce it.

Request

Please make destroy() remove the object even with this runtime behavior. Options include tearing down without ctx.abort(), or doing so until the runtime issue is fixed. Wiping storage and returning from alarm() without aborting left 0 of 4 objects behind in our test.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    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