# 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](/docs/pricing#paused-storage), checked 26 October 2026). ## Watch a directory ```ts check import { 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(); ``` ```python check from withruntime import Sandbox with 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: ```bash no-run runtime 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](/docs/mcp)). ## After a pause When its sandbox pauses, a watch stops delivering. It resumes from where it stopped: ```ts check import { 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](/docs/observability#events), 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. ```ts check import { 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](/integrations/claude-code). - A dev server's source folder is watched to rebuild or reload a [preview](/use-cases/preview-agent-built-apps). - A grader collects each file a student's program writes ([grade student code](/use-cases/grade-student-code)). The SDK reference is at [watch files](/docs/javascript#watch-files). Facts on this page were checked on 26 October 2026.