Skip to content

Conversation

@whoiskatrin
Copy link
Collaborator

Adds real-time file watching using Linux's native inotify via inotifywait.

API


// High-level 
const watcher = await sandbox.watch('/app/src', {
  recursive: true,
  include: ['*.ts', '*.js'],
  onEvent: (event) => console.log(event.type, event.path),
});
await watcher.stop();
// Low-level 
const stream = await sandbox.watchStream(path, options);
await sandbox.stopWatch(watchId);
await sandbox.listWatches();

Implementation

  • WatchService in container spawns inotifywait and streams SSE events
  • FileWatch class uses state machine pattern for lifecycle management
  • Default excludes: .git, node_modules, .DS_Store

@changeset-bot
Copy link

changeset-bot bot commented Jan 2, 2026

🦋 Changeset detected

Latest commit: 5f87bbe

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@cloudflare/sandbox Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

…ing or encoding

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
agents-git-bot bot pushed a commit to cloudflare/cloudflare-docs that referenced this pull request Jan 2, 2026
Documents the new sandbox.watch() method that enables real-time filesystem
monitoring using Linux inotify. Includes:

- API reference for watch() method in files.mdx
- Comprehensive how-to guide with practical examples
- Cross-references between related documentation

This documentation covers:
- Basic file watching with callbacks
- Filtering by file type with glob patterns
- Event type filtering (create, modify, delete, rename)
- Cancellation with AbortController
- Hot reload and log monitoring examples
- Best practices and troubleshooting

Synced from cloudflare/sandbox-sdk#324
@pkg-pr-new
Copy link

pkg-pr-new bot commented Jan 2, 2026

Open in StackBlitz

npm i https://pkg.pr.new/cloudflare/sandbox-sdk/@cloudflare/sandbox@324

commit: 5f87bbe

@github-actions
Copy link
Contributor

github-actions bot commented Jan 2, 2026

🐳 Docker Images Published

Default:

FROM cloudflare/sandbox:0.0.0-pr-324-11723aa

With Python:

FROM cloudflare/sandbox:0.0.0-pr-324-11723aa-python

With OpenCode:

FROM cloudflare/sandbox:0.0.0-pr-324-11723aa-opencode

Version: 0.0.0-pr-324-11723aa

Use the -python variant if you need Python code execution, or -opencode for the variant with OpenCode AI coding agent pre-installed.


📦 Standalone Binary

For arbitrary Dockerfiles:

COPY --from=cloudflare/sandbox:0.0.0-pr-324-11723aa /container-server/sandbox /sandbox
ENTRYPOINT ["/sandbox"]

Download via GitHub CLI:

gh run download 20676656862 -n sandbox-binary

Extract from Docker:

docker run --rm cloudflare/sandbox:0.0.0-pr-324-11723aa cat /container-server/sandbox > sandbox && chmod +x sandbox

claude[bot]

This comment was marked as outdated.

claude[bot]

This comment was marked as outdated.

claude[bot]

This comment was marked as outdated.

@whoiskatrin whoiskatrin marked this pull request as ready for review January 2, 2026 19:55
@whoiskatrin whoiskatrin marked this pull request as draft January 2, 2026 20:57
…ses. Update tests to validate new event parsing logic and ensure proper handling of inotifywait output.
claude[bot]

This comment was marked as outdated.

Copy link
Contributor

@claude claude bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This PR adds real-time file watching using Linux's inotify. The implementation follows the three-layer architecture correctly with good separation of concerns. However, there are critical error handling issues that need to be addressed before merging.

Critical Issues

1. Silent process cleanup failures

watch-service.ts:383-387 - The finally block swallows all process.kill() errors:

finally {
  try {
    proc.kill();
  } catch {
    // Process may already be dead
  }
  self.activeWatches.delete(watchId);
}

This hides permission errors, system failures, and other unexpected issues. Users won't know if cleanup failed, potentially leading to zombie processes. Log unexpected errors (not just ESRCH).

2. FileWatch.stop() fails silently

sandbox.ts:311-330 - Multiple silent failures:

  • Lines 313, 330: loopPromise.catch(() => {}) swallows all stream errors
  • Lines 321-327: Server-side stop failures only logged, never surfaced to user

Users call await watcher.stop() and get success even when server-side cleanup failed. This leaves watches active on the server.

3. Establishment promise can hang forever

sandbox.ts:168-182 - If the watch is cancelled (via AbortSignal) during establishment before receiving the "watching" event, the establishment promise never resolves or rejects. The AbortSignal handler (lines 1420-1426) doesn't call establishedReject(), causing FileWatch.create() to hang indefinitely.

4. Weak type guard allows undefined access

sandbox.ts:228-233 - isFileWatchSSEEvent only checks that type is a string, but doesn't validate required properties exist:

if (typeof obj.type !== 'string') return false;
return ['watching', 'event', 'error', 'stopped'].includes(obj.type);

This allows accessing event.watchId (line 255) or event.path (line 265) when they might be undefined, causing runtime errors.

Important Issues

5. Comments reference historical context

watch-service.ts:374-377 - Per CLAUDE.md, avoid phrases like "to avoid", "to prevent", "expected case":

// Expected case: process already exited (no longer running)
// Unexpected: permission errors, system issues

Describe current behavior, not why the code exists.

6. No validation of watchId parameter

watch-client.ts:50-61 - stopWatch() sends user input directly to API without checking it's a non-empty string.

7. Malformed events silently dropped

sandbox.ts:240-244, 246-248 - Malformed JSON and invalid event structures are silently ignored with no logging. Users won't know events are being lost.

Test Coverage Gaps (High Priority)

8. Missing: Concurrent watch operations

No tests for:

  • Multiple watches on same directory
  • stopAllWatches() called while events streaming
  • Rapid start/stop cycles

The stopAllWatches() implementation could have race conditions with concurrent modifications to the activeWatches map.

9. Missing: inotifywait crash handling

What happens when inotifywait exits unexpectedly (permission denied, kernel limit reached)? The code monitors stderr but no test verifies error events are sent before stream closes.

10. Missing: Stream cancellation during active events

No test verifies calling reader.cancel() while events are streaming properly kills the process and cleans up the watch from activeWatches.

Positive Observations

  • Follows three-layer architecture correctly
  • Strong security focus: comprehensive ReDoS prevention tests (watch-service.test.ts:84-160)
  • Proper logger injection via constructor
  • Good use of ErrorCode enum
  • FileWatch state machine is well-designed
  • Comprehensive E2E tests for happy path scenarios

Verdict

The architecture and happy path implementation are solid, but the error handling needs significant work. The silent failures will make debugging very difficult for users and could lead to resource leaks.

Recommendations before merge:

  1. Fix the 4 critical issues (especially #2 and #3 which affect user experience directly)
  2. Add logging to error paths (issues #1, #7)
  3. Add tests for concurrent operations and error scenarios (issues #8, #9, #10)

Let me know if you'd like me to help with any of these fixes!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant