From c5db3ebae2db1688e0824fbdc118518edc175001 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 17 Nov 2025 10:54:05 +0000 Subject: [PATCH 1/3] Document destroy() behavior in scheduled tasks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updates documentation to reflect that destroy() can now be safely called within scheduled task callbacks. The Agent properly handles cleanup by: - Setting an internal flag to skip remaining database updates - Yielding ctx.abort() to the event loop for clean alarm handler completion This addresses the fix in cloudflare/agents#653 where calling destroy() inside a schedule previously caused errors. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../docs/agents/api-reference/schedule-tasks.mdx | 6 ++++++ src/content/docs/agents/concepts/agent-class.mdx | 13 +++++++++++++ 2 files changed, 19 insertions(+) diff --git a/src/content/docs/agents/api-reference/schedule-tasks.mdx b/src/content/docs/agents/api-reference/schedule-tasks.mdx index 0174d45792a..50b2b8b0693 100644 --- a/src/content/docs/agents/api-reference/schedule-tasks.mdx +++ b/src/content/docs/agents/api-reference/schedule-tasks.mdx @@ -11,6 +11,12 @@ An Agent can schedule tasks to be run in the future by calling `this.schedule(wh Scheduled tasks can do anything a request or message from a user can: make requests, query databases, send emails, read+write state: scheduled tasks can invoke any regular method on your Agent. +:::note[Calling destroy() in scheduled tasks] + +You can safely call `this.destroy()` within a scheduled task callback. The Agent will properly handle cleanup and eviction, skipping any remaining database updates after the destroy flag is set. + +::: + ### Scheduling tasks You can call `this.schedule` within any method on an Agent, and schedule tens-of-thousands of tasks per individual Agent: diff --git a/src/content/docs/agents/concepts/agent-class.mdx b/src/content/docs/agents/concepts/agent-class.mdx index 86f130d5f47..0bccb7bc61f 100644 --- a/src/content/docs/agents/concepts/agent-class.mdx +++ b/src/content/docs/agents/concepts/agent-class.mdx @@ -432,6 +432,8 @@ class MyAgent extends Agent { `this.destroy()` drops all tables, deletes alarms, clears storage, and aborts the context. To ensure that the Durable Object is fully evicted, `this.ctx.abort()` is called, which throws an uncatchable error that will show up in your logs (read more about it in [abort()](/durable-objects/api/state/#abort)). +The `destroy()` method can be safely called within scheduled tasks. When called from within a schedule callback, the Agent sets an internal flag to skip any remaining database updates, and yields `ctx.abort()` to the event loop to ensure the alarm handler completes cleanly before the Agent is evicted. + ```ts class MyAgent extends Agent { async onStart() { @@ -443,6 +445,17 @@ class MyAgent extends Agent { // This wipes everything! await this.destroy(); } + + async selfDestruct() { + // Safe to call from within a scheduled task + await this.schedule(60, "destroyAfterDelay", {}); + } + + async destroyAfterDelay() { + // This will safely destroy the Agent even when + // called from within the alarm handler + await this.destroy(); + } } ``` From f8b03cdc486f6274803fa348c4008ded0100a222 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 17 Nov 2025 10:54:39 +0000 Subject: [PATCH 2/3] Document destroy() behavior in scheduled tasks Updates documentation to reflect that destroy() can now be safely called from within scheduled task callbacks. The Agent SDK now: - Sets an internal flag to prevent database updates after destruction - Defers ctx.abort() to the event loop to allow handlers to complete Related to cloudflare/agents#653 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../docs/agents/api-reference/schedule-tasks.mdx | 6 ++++++ src/content/docs/agents/concepts/agent-class.mdx | 10 +++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/content/docs/agents/api-reference/schedule-tasks.mdx b/src/content/docs/agents/api-reference/schedule-tasks.mdx index 50b2b8b0693..2fe02c46f42 100644 --- a/src/content/docs/agents/api-reference/schedule-tasks.mdx +++ b/src/content/docs/agents/api-reference/schedule-tasks.mdx @@ -80,6 +80,12 @@ Each task is mapped to a row in the Agent's underlying [SQLite database](/durabl ::: +:::note[Destroying an Agent from a scheduled task] + +You can safely call `this.destroy()` from within a scheduled task callback. The Agent SDK handles this scenario by setting an internal flag to prevent database updates after destruction and deferring the context abort to allow the alarm handler to complete cleanly. This prevents errors that would otherwise occur when the alarm handler attempts to update the schedule table after storage has been cleared. + +::: + ### Managing scheduled tasks You can get, cancel and filter across scheduled tasks within an Agent using the scheduling API: diff --git a/src/content/docs/agents/concepts/agent-class.mdx b/src/content/docs/agents/concepts/agent-class.mdx index 0bccb7bc61f..f69d419eee9 100644 --- a/src/content/docs/agents/concepts/agent-class.mdx +++ b/src/content/docs/agents/concepts/agent-class.mdx @@ -430,7 +430,9 @@ class MyAgent extends Agent { ### `this.destroy` -`this.destroy()` drops all tables, deletes alarms, clears storage, and aborts the context. To ensure that the Durable Object is fully evicted, `this.ctx.abort()` is called, which throws an uncatchable error that will show up in your logs (read more about it in [abort()](/durable-objects/api/state/#abort)). +`this.destroy()` drops all tables, deletes alarms, clears storage, and aborts the context. To ensure that the Durable Object is fully evicted, `this.ctx.abort()` is called asynchronously using `setTimeout()` to allow any currently executing handlers (like scheduled tasks) to complete their cleanup operations before the context is aborted. + +This means `this.ctx.abort()` throws an uncatchable error that will show up in your logs, but it does so after yielding to the event loop (read more about it in [abort()](/durable-objects/api/state/#abort)). The `destroy()` method can be safely called within scheduled tasks. When called from within a schedule callback, the Agent sets an internal flag to skip any remaining database updates, and yields `ctx.abort()` to the event loop to ensure the alarm handler completes cleanly before the Agent is evicted. @@ -459,6 +461,12 @@ class MyAgent extends Agent { } ``` +:::note[Using destroy() in scheduled tasks] + +You can safely call `this.destroy()` from within a scheduled task callback. The Agent SDK sets an internal flag to prevent database updates after destruction and defers the context abort to ensure the alarm handler completes cleanly. + +::: + ### Routing The `Agent` class re-exports PartyKit's [addressing helpers](#addressing) as `getAgentByName` and `routeAgentRequest`. From e62d9493c53cb5ff533951c95c16f57500038726 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 17 Nov 2025 10:56:46 +0000 Subject: [PATCH 3/3] Document destroy() behavior in scheduled tasks and immediate scheduling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updates schedule-tasks.mdx to reflect changes from cloudflare/agents#653: - Add example showing immediate task scheduling (delay = 0) - Document safe usage of destroy() within scheduled callbacks - Explain that Agent SDK defers destruction to ensure scheduled tasks complete Addresses the fix for issue #616 where calling destroy() inside a schedule would cause errors and retry loops. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../agents/api-reference/schedule-tasks.mdx | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/content/docs/agents/api-reference/schedule-tasks.mdx b/src/content/docs/agents/api-reference/schedule-tasks.mdx index 2fe02c46f42..bfb6079a95a 100644 --- a/src/content/docs/agents/api-reference/schedule-tasks.mdx +++ b/src/content/docs/agents/api-reference/schedule-tasks.mdx @@ -54,6 +54,9 @@ You can schedule tasks in multiple ways: ```ts +// schedule a task to run immediately +let task = await this.schedule(0, "someTask", { message: "hello" }); + // schedule a task to run in 10 seconds let task = await this.schedule(10, "someTask", { message: "hello" }); @@ -116,3 +119,40 @@ let tasks = this.getSchedules({ ``` + +### Lifecycle management with scheduled tasks + +When working with scheduled tasks, you can safely call `this.destroy()` from within a scheduled callback to terminate the Agent instance after completing a task. The Agent SDK properly handles the cleanup process to ensure scheduled tasks complete successfully before the Agent is evicted. + + + +```ts +export class SchedulingAgent extends Agent { + async onRequest(request) { + // Schedule a self-destructing task + await this.schedule(60, "cleanupAndDestroy"); + return new Response("Agent will self-destruct in 60 seconds"); + } + + async cleanupAndDestroy(data) { + // Perform final cleanup operations + await this.sendEmail({ + to: "admin@example.com", + subject: "Task completed", + body: "Agent task finished, shutting down" + }); + + // Safely destroy the Agent instance + // The destroy operation will complete after this callback finishes + await this.destroy(); + } +} +``` + + + +:::note + +When `destroy()` is called from within a scheduled task, the Agent SDK defers the destruction to ensure the scheduled callback completes successfully. The Agent instance will be evicted immediately after the callback finishes executing. + +:::