diff --git a/src/content/changelog/durable-objects/2026-08-13-durable-objects-deferred-code-updates.mdx b/src/content/changelog/durable-objects/2026-08-13-durable-objects-deferred-code-updates.mdx new file mode 100644 index 00000000000..f5f22893271 --- /dev/null +++ b/src/content/changelog/durable-objects/2026-08-13-durable-objects-deferred-code-updates.mdx @@ -0,0 +1,47 @@ +--- +title: Defer code updates until active Durable Objects hibernate +description: Set a code update strategy so active Durable Objects finish their current work before a code update replaces them. +products: + - durable-objects + - workers +date: 2026-08-13 +--- + +import { WranglerConfig, PackageManagers } from "~/components"; + +{/* TODO: Confirm the launch date and compatibility date before publication. */} + +Durable Objects can stay active across requests, allowing you to run long-running [agents](/agents/), maintain [long-lived WebSocket connections](/durable-objects/best-practices/websockets/) and coordinate state across many clients. This is possible because each Durable Object is globally unique, and this means it must run only one version of your code at a time. Previously, when you deployed a change to your Durable Objects code, that change would be applied immediately, which could interrupt in-flight work and terminate open connections. + +You can now set a Durable Object's code update strategy to `deferred`, which tells Cloudflare to wait for the Durable Object to hibernate before it applies the update. This lets in-flight work finish before code updates and keeps open WebSocket connections that use the [Hibernation API](/durable-objects/examples/websocket-hibernation-server/) connected. + +Add `code_update_strategy` to `durable_objects` in your Wrangler configuration: + + + +```jsonc +{ + "durable_objects": { + "bindings": [ + { + "name": "CHAT_ROOM", + "class_name": "ChatRoom", + }, + ], + "code_update_strategy": { + "mode": "deferred", + "max_delay": 60, + }, + }, +} +``` + + + +`max_delay` is in seconds, up to a maximum of `300` (five minutes). Omit it to use the default for your compatibility date. Set `mode` to `immediate` — or pass `--durable-objects-code-update-mode immediate` for a single deployment — to apply an update right away, for example during an incident: + + + +A `deferred` code update is best-effort, not a guarantee. Cloudflare can still reset an object before `max_delay` is reached. + +For more information, including how this interacts with gradual deployments and the REST API, refer to [Defer code updates until hibernation](/durable-objects/deployments/durable-objects-code-updates/). diff --git a/src/content/docs/durable-objects/best-practices/access-durable-objects-storage.mdx b/src/content/docs/durable-objects/best-practices/access-durable-objects-storage.mdx index 00eb61c9d6f..3f64fc41912 100644 --- a/src/content/docs/durable-objects/best-practices/access-durable-objects-storage.mdx +++ b/src/content/docs/durable-objects/best-practices/access-durable-objects-storage.mdx @@ -17,7 +17,13 @@ storage. Durable Objects Storage API provides access to a Durable Object's attached storage. -A Durable Object's [in-memory state](/durable-objects/reference/in-memory-state/) is preserved as long as the Durable Object is not evicted from memory. Inactive Durable Objects with no incoming request traffic can be evicted. There are normal operations like [code deployments](/workers/versions-and-deployments/) that trigger Durable Objects to restart and lose their in-memory state. For these reasons, you should use Storage API to persist state durably on disk that needs to survive eviction or restart of Durable Objects. +A Durable Object's [in-memory state](/durable-objects/reference/in-memory-state/) is preserved as long as the Durable Object remains in memory. Inactive Durable Objects with no incoming request traffic can be evicted. + +When you deploy a code change to Durable Objects, by default this change is immediately applied — Cloudflare creates a fresh isolate that runs the new version of your code, and any in-memory state is discarded. + +To give your application time to finish in-flight work or persist in-memory state to [SQLite](/durable-objects/api/sqlite-storage-api/) or to external storage, you can set a Durable Object's [code update strategy](/durable-objects/deployments/durable-objects-code-updates/) to `deferred`. If you configure a `deferred` code update strategy, when you deploy a code change, Cloudflare will wait for an active Durable Object to become inactive or to "hibernate", and only once this happens or the maximum delay is exceeded, Cloudflare will create a fresh isolate that runs the new version of your code, discarding any in-memory state. + +Just like other compute environments where compute instances do not live forever, you should persist any state that must be durable across code deployments to Durable Storage. Durable Objects makes this simple — each individual Durable Object has its own SQLite database that you can read from and write to synchronously. ## Access storage diff --git a/src/content/docs/durable-objects/best-practices/websockets.mdx b/src/content/docs/durable-objects/best-practices/websockets.mdx index 4f598aaeb17..6fcc762a8bd 100644 --- a/src/content/docs/durable-objects/best-practices/websockets.mdx +++ b/src/content/docs/durable-objects/best-practices/websockets.mdx @@ -715,9 +715,13 @@ Configure your Wrangler file with a Durable Object [binding](/durable-objects/ge A full example is available in [Build a WebSocket server](/durable-objects/examples/websocket-server/). -:::caution[WebSocket disconnection on deploy] +:::caution[WebSocket disconnection during code updates] -Code updates disconnect all WebSockets. Deploying a new version restarts every Durable Object, which disconnects any existing connections. +Connections accepted with the WebSocket Hibernation API remain connected when a Durable Object adopts new code after hibernating. + +A forced code update disconnects WebSockets attached to the running instance. This happens when a `deferred` code update's `max_delay` is reached before hibernation, or when the code update strategy is set to `immediate`. The Web Standard WebSocket API prevents hibernation, so its connections are disconnected when the code update is applied. + +Refer to [Defer code updates until hibernation](/durable-objects/deployments/durable-objects-code-updates/) for more information. ::: diff --git a/src/content/docs/durable-objects/concepts/durable-object-lifecycle.mdx b/src/content/docs/durable-objects/concepts/durable-object-lifecycle.mdx index 3f26619bf43..220632ef079 100644 --- a/src/content/docs/durable-objects/concepts/durable-object-lifecycle.mdx +++ b/src/content/docs/durable-objects/concepts/durable-object-lifecycle.mdx @@ -86,7 +86,7 @@ A Durable Object incurs charges only when it is **actively running in-memory**, Durable Objects will occasionally shut down and objects are restarted, which will run your Durable Object class constructor. This can happen for various reasons, including: -- New Worker [deployments](/workers/versions-and-deployments/) with code updates +- Worker code updates that use an `immediate` code update strategy, or a `deferred` strategy that reaches its `max_delay` - Lack of requests to an object following the state transitions documented above - Cloudflare updates to the Workers runtime system - Workers runtime decisions on where to host objects @@ -94,14 +94,18 @@ Durable Objects will occasionally shut down and objects are restarted, which wil When a Durable Object is shut down, the object instance is automatically restarted and new requests are routed to the new instance. In-flight requests are handled as follows: - **HTTP & RPC requests**: In-flight requests are allowed to finish if they do not access a Durable Object's storage. If a request attempts to access a Durable Object's storage, it will be stopped immediately and return an error to maintain Durable Objects global uniqueness property. When the Worker runtime system is being updated, in-flight requests have up to 30 seconds to complete. -- **WebSocket connections**: WebSocket requests are terminated automatically during shutdown. This is so that the new instance can take over the connection as soon as possible. +- **WebSocket connections**: When a Durable Object shuts down, WebSocket connections are terminated automatically. This allows the new instance to take over the connection as soon as possible. - **Other invocations (email, cron)**: Other invocations are treated similarly to HTTP requests. It is important to ensure that any services using Durable Objects are designed to handle the possibility of a Durable Object being shut down. ### Code updates -When your Durable Object code is updated, your Worker and Durable Objects are released globally in an eventually consistent manner. This will cause a Durable Object to shut down, with the behavior described above. Updates can also create a situation where a request reaches a new version of your Worker in one location, and calls to a Durable Object still running a previous version elsewhere. Refer to [Code updates](/durable-objects/platform/known-issues/#code-updates) for more information about handling this scenario. +[Deploying a new Worker version](/workers/versions-and-deployments/) is not delayed by a code update strategy — the deployment itself completes immediately. What a code update strategy controls is when each already-active Durable Object adopts that new version. + +A `deferred` code update strategy lets an active Durable Object keep serving requests, storage operations, and Hibernation API WebSockets on its current code until it hibernates, instead of shutting it down immediately. If the object is still active when `max_delay` is reached, Cloudflare shuts it down and applies the update. + +Refer to [Defer code updates until hibernation](/durable-objects/deployments/durable-objects-code-updates/) to configure a code update strategy and understand how it interacts with gradual deployments. ### Working without shutdown hooks diff --git a/src/content/docs/durable-objects/reference/durable-object-gradual-deployments.mdx b/src/content/docs/durable-objects/deployments/durable-object-gradual-deployments.mdx similarity index 95% rename from src/content/docs/durable-objects/reference/durable-object-gradual-deployments.mdx rename to src/content/docs/durable-objects/deployments/durable-object-gradual-deployments.mdx index e2bb1684310..560c997599e 100644 --- a/src/content/docs/durable-objects/reference/durable-object-gradual-deployments.mdx +++ b/src/content/docs/durable-objects/deployments/durable-object-gradual-deployments.mdx @@ -3,7 +3,7 @@ pcx_content_type: navigation title: Gradual Deployments external_link: /workers/versions-and-deployments/gradual-deployments/#gradual-deployments-for-durable-objects sidebar: - order: 10 + order: 2 head: [] description: Gradually deploy changes to Durable Objects. products: diff --git a/src/content/docs/durable-objects/deployments/durable-objects-code-updates.mdx b/src/content/docs/durable-objects/deployments/durable-objects-code-updates.mdx new file mode 100644 index 00000000000..2107fcf3d16 --- /dev/null +++ b/src/content/docs/durable-objects/deployments/durable-objects-code-updates.mdx @@ -0,0 +1,190 @@ +--- +title: Defer code updates until hibernation +description: Configure a Durable Object code update strategy so active Durable Objects finish their current work before a code update replaces them. +pcx_content_type: concept +sidebar: + order: 1 +products: + - durable-objects +--- + +import { WranglerConfig, PackageManagers } from "~/components"; + +{/* TODO: Confirm the compatibility date and launch date before publication. + TODO: Confirm the `code_update_strategy` REST API field name and shape with the EWC/API + team. The engineering spec still calls this field `durable_objects_hibernation_timeout` + and uses a duration string. This page follows Brendan Irvine-Broque's proposal instead + (`code_update_strategy` with `mode` and `max_delay`). Update this page once the spec and + schema catch up. */} + +When you deploy new code, Cloudflare applies the update to every Durable Object it affects. By default, this happens immediately: Cloudflare resets each active Durable Object right away, which drops in-flight requests, closes WebSocket connections, and interrupts storage operations, because a Durable Object can only run one version of your code at a time. + +Set a Durable Object's code update strategy to `deferred` to let an active object keep running its current code until it hibernates, instead of resetting it right away. Cloudflare still applies the update once the object hibernates, or once the strategy's maximum delay is reached, whichever comes first. + +## Code update strategy + +| Mode | Default `max_delay` | Behavior | +| ----------- | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | +| `deferred` | 30 seconds | Cloudflare waits for an active Durable Object to hibernate before applying the update. If the object does not hibernate within `max_delay`, Cloudflare resets it and applies the update. | +| `immediate` | Not applicable | Cloudflare resets an active Durable Object and applies the update right away, without waiting for it to hibernate. | + +Durable Objects code update strategy depends on your Workers compatibility date: + +- Workers with a compatibility date **before** `COMPATIBILITY_DATE` default to `immediate`. +- Workers with a compatibility date **on or after** `COMPATIBILITY_DATE` default to `deferred` with a 30-second `max_delay`. + +An explicit `code_update_strategy` — whether in your Wrangler configuration, passed as a CLI flag, or sent through the REST API — always overrides the compatibility-date default. + +## How it works + +1. You deploy a code change. +2. A client makes a request to a Durable Object that is not running, either an inactive object or a new object. This uses the latest version of your code. +3. Some Durable Objects are currently active — they are performing work or holding open connections. If their code update strategy is `deferred`, these objects continue running their existing version of your code. They keep handling requests, events, storage operations, and messages on WebSockets accepted with the [Hibernation API](/durable-objects/best-practices/websockets/#durable-objects-hibernation-websocket-api). +4. If an active Durable Object hibernates before `max_delay` is reached, Cloudflare applies the new version the next time that Durable Object "wakes up" in response to an incoming request or WebSocket message. Hibernated WebSocket connections stay connected throughout. +5. If a Durable Object is still active when `max_delay` is reached, Cloudflare resets it and applies the update. + +```mermaid +flowchart LR + A["You deploy a code change"] --> B{"Is the Durable Object
currently running?"} + B -->|Inactive| C["Adopts the new version
on its next request"] + B -->|"Active, mode is deferred"| D["Starts its max_delay timer,
keeps running its current version"] + D --> E{"Hibernates before
max_delay ends?"} + E -->|Yes| F["Adopts the new version
on next request"] + E -->|"No, max_delay reached"| G["Cloudflare resets it and
applies the update"] +``` + +Deploying again before an object hibernates does not restart or extend its `max_delay`. The object applies the latest deployed version once it hibernates or the original `max_delay` is reached, and it might skip versions you deployed in between. + +## Configure a code update strategy + +Add `code_update_strategy` alongside `bindings` in the `durable_objects` object of your [Wrangler configuration file](/workers/wrangler/configuration/#durable-objects): + + + +```jsonc +{ + "durable_objects": { + "bindings": [ + { + "name": "CHAT_ROOM", + "class_name": "ChatRoom", + }, + ], + "code_update_strategy": { + "mode": "deferred", + "max_delay": 60, + }, + }, +} +``` + + + +`max_delay` is in seconds and only applies when `mode` is `deferred`. The maximum is `300` (five minutes). Omit `max_delay` to use the default for your compatibility date. + +## What each configuration does + +| `code_update_strategy` | Result | +| --------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | +| *(omitted)*, compatibility date before `COMPATIBILITY_DATE` | Defaults to `immediate`. | +| *(omitted)*, compatibility date on or after `COMPATIBILITY_DATE` | Defaults to `deferred` with a 30-second `max_delay`. | +| `{ "mode": "deferred" }` | Cloudflare waits up to the default `max_delay` (30 seconds) for the object to hibernate. | +| `{ "mode": "deferred", "max_delay": 0 }` | Valid, but Cloudflare waits zero seconds — behaves identically to `immediate`. | +| `{ "mode": "deferred", "max_delay": 120 }` | Cloudflare waits up to 120 seconds for the object to hibernate before applying the update. | +| `{ "mode": "immediate" }` | Cloudflare applies the update immediately, without waiting for the object to hibernate. | +| `{ "mode": "immediate", "max_delay": 60 }` | **Invalid.** Wrangler and the REST API reject this configuration — `max_delay` has no effect when `mode` is `immediate`. | + +## Override the mode for one deployment + +Use the flag when a single deployment needs different behavior than your checked-in Wrangler configuration — for example, applying an `immediate` update during an incident without editing and re-committing `code_update_strategy`. Pass `--durable-objects-code-update-mode` to `wrangler versions deploy` or `wrangler versions rollback` (or their `wrangler deploy` and `wrangler rollback` aliases) to override the configured mode for a single deployment: + + + +The flag accepts `deferred` or `immediate` and overrides only `mode`. To use a `max_delay` other than the default, set `code_update_strategy` in your Wrangler configuration or in the REST API request instead — the flag does not accept `max_delay`. + +Wrangler resolves `mode` in this order: + +1. The `--durable-objects-code-update-mode` flag. +2. `durable_objects.code_update_strategy.mode` in your Wrangler configuration. +3. The default for your compatibility date. + +## Use the REST API + +Add `code_update_strategy` to a [Create deployment](/api/resources/workers/subresources/scripts/subresources/deployments/methods/create/) request. This field belongs to the deployment, not the Worker version. + +```bash +curl "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/workers/scripts/$SCRIPT_NAME/deployments" \ + --request POST \ + --header "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \ + --header "Content-Type: application/json" \ + --data '{ + "strategy": "percentage", + "versions": [ + { + "version_id": "f3b5b2d2-75d6-4c3f-ae3d-4df2f9cfeb7a", + "percentage": 100 + } + ], + "code_update_strategy": { + "mode": "deferred", + "max_delay": 60 + } + }' +``` + +Cloudflare returns the effective strategy in create, get, and list deployment responses, including the compatibility-date default when you omit the field: + +```json +{ + "result": { + "id": "184e7530-050e-4a03-8d9f-8f391fb4f204", + "code_update_strategy": { + "mode": "deferred", + "max_delay": 30 + } + }, + "success": true, + "errors": [], + "messages": [] +} +``` + +To apply an update immediately through the REST API, set `"code_update_strategy": { "mode": "immediate" }`. + +Use `GET /accounts/{account_id}/workers/scripts/{script_name}/deployments/{deployment_id}` to check the strategy Cloudflare applied to one deployment, or `GET /accounts/{account_id}/workers/scripts/{script_name}/deployments` to list deployment history. + +:::note +The REST API field name above reflects the proposed naming and is not yet final. Check back here or in the [API reference](/api/resources/workers/subresources/scripts/subresources/deployments/methods/create/) before you build automation against it. +::: + +## Apply an update immediately + +Set `code_update_strategy` to `{ "mode": "immediate" }`, or pass `--durable-objects-code-update-mode immediate` for a single deployment, when waiting is worse than resetting. Common cases: + +- You need to ship a correctness or security fix immediately, even if it interrupts active objects. +- Some of your objects rarely or never hibernate, and you need to force them onto new code, or reduce their duration costs, without waiting. + +Cloudflare applies an `immediate` update without waiting for the Durable Object to hibernate. + +## Gradual deployments + +During a [gradual deployment](/workers/versions-and-deployments/gradual-deployments/with-durable-objects/), Cloudflare assigns each Durable Object to one of the versions in the deployment. Increasing a version's traffic percentage assigns more objects to that version. Only the newly assigned objects follow the mechanism described in [How it works](/durable-objects/deployments/durable-objects-code-updates/#how-it-works) above — for example, increasing a version's traffic percentage from 20% to 50% starts a `deferred` wait only for the additional 30% of objects newly assigned to it. Objects that already run that version, and objects still assigned to an earlier version, are not affected. + +Different objects can be on different `max_delay` clocks at the same time. If a later progression uses a different code update strategy, that strategy applies only to the objects that progression reassigns. + +Keep your Worker and Durable Object code forward and backward compatible for as long as any version might still be running, not just for the length of the deployment. Refer to [Code updates](/durable-objects/platform/known-issues/#code-updates). + +## Limitations + +- A `deferred` code update strategy is best-effort. Cloudflare does not guarantee that an object keeps running its current code for the entire `max_delay`. +- A `deferred` code update does not stop an object from accepting new requests, events, or storage operations while it waits to hibernate. +- An object that is still active when `max_delay` is reached resets the same way an `immediate` update does. +- A code update strategy does not delay restarts unrelated to code updates, such as process sandbox migrations, resource limits, or Workers runtime updates. +- A code update strategy does not make incompatible Worker and Durable Object versions safe to run together. Keep your interfaces forward and backward compatible across versions. + +## Related resources + +- [Lifecycle of a Durable Object](/durable-objects/concepts/durable-object-lifecycle/) +- [Gradual deployments with Durable Objects](/workers/versions-and-deployments/gradual-deployments/with-durable-objects/) +- [Code updates](/durable-objects/platform/known-issues/#code-updates) +- [Troubleshoot Durable Object resets](/durable-objects/observability/troubleshooting/#durable-object-reset-because-its-code-was-updated) diff --git a/src/content/docs/durable-objects/deployments/index.mdx b/src/content/docs/durable-objects/deployments/index.mdx new file mode 100644 index 00000000000..a81669ab4d3 --- /dev/null +++ b/src/content/docs/durable-objects/deployments/index.mdx @@ -0,0 +1,15 @@ +--- +title: Deployments +description: How Durable Objects behave during Worker deployments, including code updates and gradual deployments. +pcx_content_type: navigation +sidebar: + order: 10 + group: + hideIndex: true +products: + - durable-objects +--- + +import { DirectoryListing } from "~/components"; + + diff --git a/src/content/docs/durable-objects/observability/troubleshooting.mdx b/src/content/docs/durable-objects/observability/troubleshooting.mdx index 642a88cd3ab..3eed75d9d87 100644 --- a/src/content/docs/durable-objects/observability/troubleshooting.mdx +++ b/src/content/docs/durable-objects/observability/troubleshooting.mdx @@ -43,9 +43,11 @@ There is a limit on how quickly you can create new [stubs](/durable-objects/api/ ### Durable Object reset because its code was updated. -Reset in error messages refers to in-memory state. Any durable state that has already been successfully persisted via `state.storage` is not affected. +This error occurs when a code update is applied before a Durable Object reaches hibernation. This includes an `immediate` code update strategy and `deferred` deployments whose `max_delay` is reached before the object hibernates. -Refer to [Global Uniqueness](/durable-objects/platform/known-issues/#global-uniqueness). +Reset in error messages refers to in-memory state. Any durable state already persisted with `ctx.storage` is not affected. Make operations retryable and persist important state so a forced update can safely restart the object. + +Refer to [Defer code updates until hibernation](/durable-objects/deployments/durable-objects-code-updates/) and [Global uniqueness](/durable-objects/platform/known-issues/#global-uniqueness). ### Durable Object storage operation exceeded timeout which caused object to be reset. diff --git a/src/content/docs/durable-objects/platform/known-issues.mdx b/src/content/docs/durable-objects/platform/known-issues.mdx index 8023941f453..d48a8b942f4 100644 --- a/src/content/docs/durable-objects/platform/known-issues.mdx +++ b/src/content/docs/durable-objects/platform/known-issues.mdx @@ -22,9 +22,11 @@ A Durable Object may be replaced in the event of a network partition or a softwa ## Code updates -Code changes for Workers and Durable Objects are released globally in an eventually consistent manner. Because each Durable Object is globally unique, the situation can arise that a request arrives to the latest version of your Worker (running in one part of the world), which then calls to a unique Durable Object running the previous version of your code for a short period of time (typically seconds to minutes). If you create a [gradual deployment](/workers/versions-and-deployments/gradual-deployments/), this period of time is determined by how long your live deployment is configured to use more than one version. +Code changes for Workers and Durable Objects are released globally in an eventually consistent manner. A request can reach the latest version of your Worker, which then calls a unique Durable Object running a previous version of your code. -For this reason, it is best practice to ensure that API changes between your Workers and Durable Objects are forward and backward compatible across code updates. +With a `deferred` [code update strategy](/durable-objects/deployments/durable-objects-code-updates/), an active Durable Object can continue running a previous version until it hibernates or its `max_delay` is reached. Gradual deployments can extend this window while your deployment uses more than one version. If you deploy multiple versions before an object hibernates, it might skip intermediate versions and use the latest version when it wakes. + +Ensure that API changes between your Workers and Durable Objects are forward and backward compatible across code updates. ## Development tools @@ -38,4 +40,4 @@ The Workers editor in the [Cloudflare dashboard](https://dash.cloudflare.com/) a Currently, when developing locally (using `npx wrangler dev`), Durable Object [alarm methods](/durable-objects/api/alarms) may fail after a hot reload (if you edit the code while the code is running locally). -To avoid this issue, when using Durable Object alarms, close and restart your `wrangler dev` command after editing your code. \ No newline at end of file +To avoid this issue, when using Durable Object alarms, close and restart your `wrangler dev` command after editing your code. diff --git a/src/content/partials/durable-objects/working-without-shutdown-hooks.mdx b/src/content/partials/durable-objects/working-without-shutdown-hooks.mdx index e9a31c50c9f..d9554937e73 100644 --- a/src/content/partials/durable-objects/working-without-shutdown-hooks.mdx +++ b/src/content/partials/durable-objects/working-without-shutdown-hooks.mdx @@ -2,7 +2,7 @@ {} --- -Durable Objects may shut down at any time due to deployments, inactivity, or runtime decisions. Rather than relying on shutdown hooks (which are not provided), design your application to write state incrementally. +A Durable Object shuts down when it becomes inactive, when a [code update strategy](/durable-objects/deployments/durable-objects-code-updates/) reaches it (immediately under `immediate`, or once it hibernates or reaches `max_delay` under `deferred`), or when Cloudflare deploys a new version of the Workers runtime. This is similar to other compute environments, where compute instances do not live forever and applications must persist state back to a database or storage system. Rather than relying on shutdown hooks (which are not provided), design your application to write state incrementally. Shutdown hooks or lifecycle callbacks that run before shutdown are not provided because Cloudflare cannot guarantee these hooks would execute in all cases, and external software may rely too heavily on these (unreliable) hooks.