Skip to content
Runtime

How to watch files for changes in a sandbox

Call sbx.files.watch(dir, onEvent, { recursive: true }). It reports each create, write, remove, rename and chmod event as it happens.

On Runtime, a file watch survives a pause and loses nothing. Reading a watch never wakes a paused sandbox. Delivery stops. After the wake, the watch continues from the same place. It does what E2B's watchDir does. It also adds include and exclude globs, batching and overflow notices. A paused sandbox costs $0.08 per decimal GB of saved state a month, and no compute (pricing, checked 26 October 2026).

Watch a directory

TypeScriptimport { Sandbox } from "withruntime";await using sbx = await Sandbox.create();const watch = await sbx.files.watch("/workspace", (event) => console.log(event.type, event.path), {  recursive: true,  exclude: ["node_modules", ".git/**"],  onNotice: (notice) => console.warn("rescan:", notice),  onExit: (reason) => console.log("watch ended:", reason),});await sbx.exec("echo hi > /workspace/note.txt");await watch.stop();
Pythonfrom withruntime import Sandboxwith Sandbox.create() as sbx:    watch = sbx.files.watch("/workspace", recursive=True, exclude=["node_modules", ".git/**"])    sbx.exec("echo hi > /workspace/note.txt")    for event in watch.get_new_events(wait_ms=2000):        print(event["type"], event["path"])    print(watch.notices)    watch.stop()

In Python, get_new_events(wait_ms=...) returns the events that have arrived. watch.events() iterates until the watch ends. A rename event carries the old path as well.

From a terminal, watch prints each change until Ctrl-C:

Terminalruntime sandboxes watch "${id}" /workspace --recursive --exclude node_modules --exclude '.git/**'runtime sandboxes watch "${id}" /workspace/src --events create,write --include '**/*.py'

An agent over MCP uses runtime_sandboxes_files_watch to start a watch, read its events after a cursor, list watches and stop one (MCP).

After a pause

When its sandbox pauses, a watch stops delivering. It resumes from where it stopped:

TypeScriptimport { Sandbox } from "withruntime";const sbx = await Sandbox.create({ idlePauseSeconds: 600 });const watch = await sbx.files.watch("/workspace/src", (e) => console.log(e.type, e.path), {  recursive: true,  onExit: async (reason) => {    if (reason === "paused") {      await sbx.wake();      watch.resume(); // nothing lost    }  },});

In Python, events() stops with exit_reason == "paused". Calling it again after the wake continues from the same place.

Options and limits

Option or limit Value
Events create, write, remove, rename (with the old path), chmod
recursive Watch subdirectories; a new directory is scanned as it appears
include, exclude Globs; an excluded directory is not watched at all
events Only these kinds, such as create,write on the CLI
Batching Repeated writes to one file in a batch come as one event with a count
Watches per sandbox At most four
timeoutMs One hour by default, then the watch ends; 0 runs until stopped
webhook Runtime sends the changes to your webhooks as sandbox.files.changed
Flood cap 5,000 events a second; the rest reported as an overflow notice, never silently
Over HTTP POST /v1/sandboxes/{id}/files/watches; events after a cursor, waitMs to 8000
Cost No charge per watch; the sandbox's measured CPU and reserved memory

Changes pushed to your server

A watch started with webhook: true needs nothing reading it. Runtime reads it and POSTs what changed to your account's webhooks, signed. It retries for about three days when your server does not answer. Nothing of yours runs in the sandbox. So a sync service that ran its own inotify watcher and an HTTP client in there can drop both. Each event lists up to 100 changes gathered over at least 2 seconds, and an account keeps up to 16 such watches.

TypeScriptimport { Sandbox } from "withruntime";await using sbx = await Sandbox.create();await sbx.files.watches.webhook("/workspace/app", { recursive: true, exclude: ["node_modules"] });

Reading it never wakes a paused sandbox. While a sandbox that pauses itself does nothing, the watch is not read at all. So it never keeps the sandbox running. The first change after that arrives within seconds of the sandbox doing work again.

Mistakes to avoid

  • Watching node_modules or .git. They change in bursts of thousands of files. Exclude them. An excluded directory is not watched at all, so its churn never reaches the watch.
  • Assuming a quiet watch means no changes. After a flood past 5,000 events a second, the watch sends an overflow notice through onNotice (or watch.notices in Python). The notice says how many were dropped. Rescan the directory when you see one.
  • A fifth watch. A sandbox runs at most four watches. To cover many folders, watch a common parent with recursive and filter with include. Do not start one watch per folder.
  • Treating onExit("paused") as the end. It means the sandbox paused. Call watch.resume() once it wakes, and the stream continues.
  • Leaving it to time out silently. A watch ends after timeoutMs, an hour by default. Handle onExit, and start a new watch if you still need it.

Where this helps

  • A coding agent edits files while your UI shows each change live, as in Claude Code in a sandbox.
  • A dev server's source folder is watched to rebuild or reload a preview.
  • A grader collects each file a student's program writes (grade student code).

The SDK reference is at watch files.

Facts on this page were checked on 26 October 2026.

Related.

100 free hoursin your first 30 days.

of a 1 GB machine

  • No credit card
  • Four sandboxes at once, 2 vCPU and 4 GiB each
  • Then prepaid credit from $10, no plan fee
Start free, no card
Start free, no card