How the Watchdog Works
Minecraft Bedrock runs at a target rate of 20 ticks per second, giving the entire game 50 milliseconds to process physics, rendering, networking, and scripts for a single tick. Because JavaScript runs synchronously on the game thread during the script phase of a tick, a poorly optimized script will directly delay the next tick and cause severe server lag. To prevent a single malfunctioning behavior pack from locking up a world or dedicated server, the engine enforces strict execution thresholds through the Script Watchdog.
The watchdog monitors two primary resources: execution time per tick (hangs) and total memory allocation within the JavaScript runtime. When your script approaches a limit, the engine logs a performance warning to the Content Log. If your script completely breaches the hard limit—such as getting stuck in a while loop that never exits or running a massive nested loop over tens of thousands of blocks in one frame—the watchdog forcefully terminates your behavior pack's script execution for the remainder of the world session.
Listening for Watchdog Termination
The `@minecraft/server` module exposes a special before-event called `system.beforeEvents.watchdogTerminate`. This event fires right before the engine kills your script context due to a hang or memory breach. By subscribing to this event, you can inspect the reason for the shutdown and, in specific circumstances, cancel the termination to keep your script alive.
Canceling `watchdogTerminate` is a temporary band-aid, not a permanent bypass. If your code is genuinely stuck in an infinite `while (true)` loop, canceling the termination event will not unstick the loop; the watchdog will simply trigger again until the engine forces a hard crash or shuts down the world. You should mainly use this listener during development to log diagnostic data, or in production to gracefully forgive an occasional, known lag spike caused by heavy initialization.
| Property / Enum | What it does | Notes |
|---|---|---|
| eventData.cancel | Boolean that stops the watchdog from terminating the script pack when set to true. | Does not fix actual infinite loops; use with caution. |
| eventData.terminateReason | Indicates why the watchdog stepped in, matching a WatchdogTerminateReason value. | Read-only property on the event payload. |
| WatchdogTerminateReason.Hang | Fires when a single tick takes too many milliseconds to finish executing. | The most common reason seen during heavy block or entity operations. |
| WatchdogTerminateReason.StackOverflow | Fires when functions call themselves recursively too deeply. | Usually caused by an accidental loop between two event listeners. |
import { system, WatchdogTerminateReason } from "@minecraft/server";
system.beforeEvents.watchdogTerminate.subscribe((eventData) => {
// Prevent the engine from killing the script pack
eventData.cancel = true;
if (eventData.terminateReason === WatchdogTerminateReason.Hang) {
console.warn("Watchdog intercepted a script hang! Execution took too long this tick.");
} else if (eventData.terminateReason === WatchdogTerminateReason.StackOverflow) {
console.warn("Watchdog intercepted a stack overflow! Check for infinite recursion.");
}
});Preventing Hangs with Job Generators
The proper way to deal with the watchdog when performing massive tasks—like scanning a 50x50x50 volume of blocks—is to spread the work across multiple game ticks instead of doing it all inside a single function call. While you can manually chunk arrays and process them with `system.runInterval`, the Scripting API provides a native tool designed specifically to cooperate with the watchdog: `system.runJob`.
To use `system.runJob`, you write your heavy loop inside a JavaScript generator function (`function*`) and place a `yield` statement inside the inner loop. Every time the generator hits `yield`, it pauses and hands control back to the Minecraft engine. The engine automatically runs as many iterations of your generator per tick as it safely can without crossing the watchdog's time budget, then pauses and resumes the rest on the next tick.
import { system, world } from "@minecraft/server";
function* clearVolumeGenerator(dimension, startPos, size) {
for (let x = 0; x < size; x++) {
for (let y = 0; y < size; y++) {
for (let z = 0; z < size; z++) {
const block = dimension.getBlock({
x: startPos.x + x,
y: startPos.y + y,
z: startPos.z + z
});
if (block && !block.isAir) {
block.setType("minecraft:air");
}
// Pause here so the engine can manage tick time
yield;
}
}
}
}
export function startClearingArea(player) {
const dim = player.dimension;
const origin = player.location;
// Schedule the generator to run safely across multiple ticks
system.runJob(clearVolumeGenerator(dim, origin, 32));
}Avoiding Memory and Recursion Limits
Timeouts are not the only thing that angers the watchdog. Each behavior pack is given a strict memory ceiling for its JavaScript context. If you cache references to thousands of `Entity` or `Block` objects in a global array or `Map` and never clean them up, garbage collection cannot free that memory, and your pack will eventually crash with an out-of-memory error. Instead of storing full API objects long-term, store lightweight primitive values like entity `id` strings or plain `{x, y, z}` coordinate objects, and fetch the live reference only when needed.
Similarly, watch out for accidental event recursion. For example, if you subscribe to an entity hurt event and apply damage to the same entity inside that callback without a guard condition, the damage triggers the hurt event again immediately. This creates an instant recursive loop that blows the call stack and triggers a `StackOverflow` termination within a single tick.
Disabling the Watchdog on Dedicated Servers
When hosting a Bedrock Dedicated Server (BDS) or testing massive technical packs locally, you may want to disable or raise the watchdog thresholds at the engine level rather than canceling the event in code. While ordinary singleplayer worlds do not expose a settings toggle for this, you can run slash commands with cheats enabled to adjust the monitor during a testing session.
Running `/script watchdog exportstats` exports a profile of your script's memory and timing spikes to help you track down which file or function is eating your tick budget. Keep in mind that disabling or suppressing watchdog limits should only be done for debugging; any pack meant for distribution to players or Realms must respect the default thresholds, as players on mobile devices or consoles cannot alter engine watchdog settings.
Which versions this applies to
Applies to Bedrock Edition 1.19.50 and newer. Both `system.beforeEvents.watchdogTerminate` and `system.runJob` are available in the stable `@minecraft/server` API (starting in 1.20.0+ stable releases) and do not require the Beta APIs experimental toggle.
When it does not work
| What you see | Why | The fix |
|---|---|---|
| [Scripting][error]-Script pack [<UUID>] has been terminated due to [Hang] (exceeded X ms). | Your script ran a heavy synchronous loop (like checking thousands of blocks or entities at once) or got stuck in a `while` loop whose exit condition never became false. | Convert large loops into generator functions (`function*`) with `yield` statements and run them via `system.runJob()`, or split the work across ticks using `system.runInterval()`. |
| The entire game freezes completely after adding `eventData.cancel = true` inside `watchdogTerminate`. | You canceled the watchdog termination while your script was trapped inside an actual infinite loop. Because the watchdog was told not to kill the script, the game thread stays permanently locked. | Remove the `watchdogTerminate` cancellation while debugging and fix the broken exit condition in your `while` or `for` loop. |
| [Scripting][warning]-Script spike detected in pack [<UUID>]: Tick took X ms. | Your code did not cross the fatal termination limit, but it took long enough on a single tick to cause noticeable server lag. | Avoid running heavy queries like `dimension.getEntities()` every single tick inside a `system.runInterval` callback; increase the interval delay to run every 5 or 20 ticks instead. |
Reference, straight from the game files
Read out of the game itself, so the ids match the version you are building for.
