Watch filesystem changes·续
const stream = await sandbox.watch("/workspace/src"); const changedFiles = new Set<string>(); let debounceTimeout: ReturnType<typeof setTimeout> | null = null;
for await (const event of parseSSEStream<FileWatchSSEEvent>(stream)) { if (event.type === "event") { changedFiles.add(event.path);
if (debounceTimeout) { clearTimeout(debounceTimeout); }
debounceTimeout = setTimeout(async () => {
console.log(Processing ${changedFiles.size} changed files...);
for (const filePath of changedFiles) {
await processFile(filePath);
}
changedFiles.clear();
debounceTimeout = null;
}, 1000);
}
}
### Watch with non-recursive mode
Watch only the top level of a directory, without descending into subdirectories:
```js
import { parseSSEStream } from "@cloudflare/sandbox";
// Only watch root-level config files
const stream = await sandbox.watch("/workspace", {
include: ["package.json", "tsconfig.json", "vite.config.ts"],
recursive: false,
});
for await (const event of parseSSEStream(stream)) {
if (event.type === "event") {
console.log("Configuration changed, rebuilding project...");
await sandbox.exec("npm run build");
}
}
import { parseSSEStream } from "@cloudflare/sandbox";
import type { FileWatchSSEEvent } from "@cloudflare/sandbox";
// Only watch root-level config files
const stream = await sandbox.watch("/workspace", {
include: ["package.json", "tsconfig.json", "vite.config.ts"],
recursive: false,
});
for await (const event of parseSSEStream<FileWatchSSEEvent>(stream)) {
if (event.type === "event") {
console.log("Configuration changed, rebuilding project...");
await sandbox.exec("npm run build");
}
}
Stop a watch
The stream ends naturally when the container sleeps or shuts down. There are two ways to stop a watch early:
Use an AbortController
Pass an AbortSignal to parseSSEStream. Aborting the signal cancels the stream reader, which propagates cleanup to the server. This is the recommended approach when you need to cancel the watch from outside the consuming loop:
import { parseSSEStream } from "@cloudflare/sandbox";
const stream = await sandbox.watch("/workspace/src");
const controller = new AbortController();
// Cancel after 60 seconds
setTimeout(() => controller.abort(), 60_000);
for await (const event of parseSSEStream(stream, controller.signal)) {
if (event.type === "event") {
console.log(`${event.eventType}: ${event.path}`);
}
}
console.log("Watch stopped");
import { parseSSEStream } from "@cloudflare/sandbox";
import type { FileWatchSSEEvent } from "@cloudflare/sandbox";
const stream = await sandbox.watch("/workspace/src");
const controller = new AbortController();
// Cancel after 60 seconds
setTimeout(() => controller.abort(), 60_000);
for await (const event of parseSSEStream<FileWatchSSEEvent>(
stream,
controller.signal,
)) {
if (event.type === "event") {
console.log(`${event.eventType}: ${event.path}`);
}
}
console.log("Watch stopped");
Break out of the loop
Breaking out of the for await loop also cancels the stream:
import { parseSSEStream } from "@cloudflare/sandbox";
const stream = await sandbox.watch("/workspace/src");
let eventCount = 0;
for await (const event of parseSSEStream(stream)) {
if (event.type === "event") {
console.log(`${event.eventType}: ${event.path}`);
eventCount++;
// Stop after 100 events
if (eventCount >= 100) {
break; // Breaking out of the loop cancels the stream
}
}
}
console.log("Watch stopped");
import { parseSSEStream } from "@cloudflare/sandbox";
import type { FileWatchSSEEvent } from "@cloudflare/sandbox";
const stream = await sandbox.watch("/workspace/src");
let eventCount = 0;
for await (const event of parseSSEStream<FileWatchSSEEvent>(stream)) {
if (event.type === "event") {
console.log(`${event.eventType}: ${event.path}`);
eventCount++;
// Stop after 100 events
if (eventCount >= 100) {
break; // Breaking out of the loop cancels the stream
}
}
}
console.log("Watch stopped");
Best practices
Use server-side filtering
Filter with include or exclude patterns rather than filtering events in JavaScript. Server-side filtering happens at the inotify level, which reduces the number of events sent over the network.
Note
include and exclude are mutually exclusive. Use one or the other, not both. If you need to watch specific file types while ignoring certain directories, use include patterns that match the files you want.
import { parseSSEStream } from "@cloudflare/sandbox";
// Efficient: filtering happens at the inotify level
const stream = await sandbox.watch("/workspace/src", {
include: ["*.ts"],
});
// Less efficient: all events are sent and then filtered in JavaScript
const stream2 = await sandbox.watch("/workspace/src");
for await (const event of parseSSEStream(stream2)) {
if (event.type === "event") {
if (!event.path.endsWith(".ts")) continue;
// Handle event
}
}
import { parseSSEStream } from "@cloudflare/sandbox";
import type { FileWatchSSEEvent } from "@cloudflare/sandbox";
// Efficient: filtering happens at the inotify level
const stream = await sandbox.watch("/workspace/src", {
include: ["*.ts"],
});
// Less efficient: all events are sent and then filtered in JavaScript
const stream2 = await sandbox.watch("/workspace/src");
for await (const event of parseSSEStream<FileWatchSSEEvent>(stream2)) {
if (event.type === "event") {
if (!event.path.endsWith(".ts")) continue;
// Handle event
}
}
Handle errors in event processing
Errors in your event handler do not stop the watch stream. Wrap handler logic in try...catch to prevent unhandled exceptions:
import { parseSSEStream } from "@cloudflare/sandbox";
const stream = await sandbox.watch("/workspace/src");
for await (const event of parseSSEStream(stream)) {
if (event.type === "event") {
try {
await handleFileChange(event.eventType, event.path);
} catch (error) {
console.error(
`Failed to handle ${event.eventType} for ${event.path}:`,
error,
);
// Continue processing events
}
}
if (event.type === "error") {
console.error("Watch error:", event.error);
}
}
import { parseSSEStream } from "@cloudflare/sandbox";
import type { FileWatchSSEEvent } from "@cloudflare/sandbox";
const stream = await sandbox.watch("/workspace/src");
for await (const event of parseSSEStream<FileWatchSSEEvent>(stream)) {
if (event.type === "event") {
try {
await handleFileChange(event.eventType, event.path);
} catch (error) {
console.error(
`Failed to handle ${event.eventType} for ${event.path}:`,
error,
);
// Continue processing events
}
}
if (event.type === "error") {
console.error("Watch error:", event.error);
}
}
Ensure directories exist before watching
Watching a non-existent path returns an error. Verify the path exists before starting a watch:
const watchPath = "/workspace/src";
const result = await sandbox.exists(watchPath);
if (!result.exists) {
await sandbox.mkdir(watchPath, { recursive: true });
}
const stream = await sandbox.watch(watchPath, {
include: ["*.ts"],
});
const watchPath = "/workspace/src";
const result = await sandbox.exists(watchPath);
if (!result.exists) {
await sandbox.mkdir(watchPath, { recursive: true });
}
const stream = await sandbox.watch(watchPath, {
include: ["*.ts"],
});
Troubleshooting
High CPU usage
If watching large directories causes performance issues:
- Use specific
includepatterns instead of watching everything - Exclude large directories like
node_modulesanddist - Watch specific subdirectories instead of the entire project
- Use
recursive: falsefor shallow monitoring
Path not found errors
All paths must exist and resolve to within /workspace. Relative paths are resolved from /workspace.
Container lifecycle
File watchers are automatically stopped when the sandbox sleeps or shuts down. If the sandbox wakes up, you must re-establish watches in your application logic.
Related resources
- File Watching API reference — Complete API documentation and types
- Manage files guide — File operations
- Background processes guide — Long-running processes
- Stream output guide — Real-time output handling
Was this helpful?
YesNo
On this page
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/sandbox/sdk/guides/file-watching/#page","headline":"Watch filesystem changes","description":"Monitor files and directories in real time with the Sandbox SDK 0.x to build development tools and automation workflows.","url":"https://developers.cloudflare.com/sandbox/sdk/guides/file-watching/","inLanguage":"en","image":"https://developers.cloudflare.com/sandbox/sdk/guides/file-watching/og.png?v=d3741a73df2e386a","dateModified":"2026-09-30","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
description: Clone repositories, manage branches, and automate Git operations with the Sandbox SDK 0.x. title: Work with Git image: https://developers.cloudflare.com/sandbox/sdk/guides/git-workflows/og.png?v=048dfbf4df6a0a20
Documentation Index
Fetch the complete documentation index at: https://developers.cloudflare.com/sandbox/llms.txt
Use this file to discover all available pages before exploring further.
Docs