---
name: oblien-watcher
description: >-
  Oblien file system watcher - real-time change events streamed over WebSocket via the Runtime API on port 9990
license: MIT
compatibility:
  - claude-code
  - cursor
  - openclaw
  - goose
  - jetbrains-ai
metadata:
  author: oblien
  version: 1.0.0
  source: https://oblien.com/docs
---

# File Watcher

Watch directories for file system changes in real time. The watcher monitors filesystem events and streams them over the [WebSocket connection](/docs/runtime-api/terminal#websocket) at `/ws`.

> **Note:** Requires the internal server to be [enabled](/docs/runtime-api/server-setup#enable-the-server). Up to **5 concurrent watchers** per workspace.


## Overview

```
1. Create a watcher via REST    →  get watcher ID
2. Open WebSocket at /ws        →  receive "ready" event
3. File changes in watched dir  →  receive "change" events
4. Delete watcher when done     →  cleanup
```

Watcher events arrive as **JSON text frames** on the `"watcher"` channel of the same WebSocket used for [terminal I/O](/docs/runtime-api/terminal#websocket).

---

## Create watcher

Start watching a directory for changes. The watcher recursively monitors all subdirectories and streams events over the WebSocket.


**SDK:**

```typescript
const rt = await client.workspaces.runtime('ws_a1b2c3d4');

const watcher = await rt.watcher.create({
  path: '/app/src',
  excludes: ['*.log', 'tmp'],
});

console.log(watcher.id);   // "1"
console.log(watcher.root); // "/app/src"
console.log(watcher.dirs); // 42
```


**REST API:**

```http
POST https://workspace.oblien.com/watchers
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
Content-Type: application/json

{
  "path": "/app/src",
  "excludes": ["*.log", "tmp"]
}
```


**cURL:**

```bash
curl -X POST "https://workspace.oblien.com/watchers" \
  -H "Authorization: Bearer $GATEWAY_JWT" \
  -H "Content-Type: application/json" \
  -d '{"path": "/app/src", "excludes": ["*.log", "tmp"]}'
```


### Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `path` | `string` | Yes | Directory to watch (recursively) |
| `excludes` | `string[]` | No | Glob patterns to exclude (merged with defaults) |

### Response

```json
{
  "id": "1",
  "root": "/app/src",
  "dirs": 42,
  "excludes": ["node_modules", ".git", "*.log", "tmp"]
}
```

### Default excludes

These patterns are always excluded, even if you don't specify any:

```
node_modules, .git, .svn, .hg, __pycache__, .pytest_cache,
.mypy_cache, .next, .nuxt, dist, build, .DS_Store, *.swp, *.swo, *~
```

Your custom `excludes` are merged with these defaults.

---

## List watchers

Get all active watchers.


**SDK:**

```typescript
const watchers = await rt.watcher.list();

for (const w of watchers) {
  console.log(`${w.id}: watching ${w.root} (${w.dirs} dirs)`);
}
```


**REST API:**

```http
GET https://workspace.oblien.com/watchers
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
```


**cURL:**

```bash
curl "https://workspace.oblien.com/watchers" \
  -H "Authorization: Bearer $GATEWAY_JWT"
```


### Response

```json
{
  "watchers": [
    {
      "id": "1",
      "root": "/app/src",
      "dirs": 42,
      "excludes": ["node_modules", ".git"]
    }
  ]
}
```

---

## Get watcher

Get info for a specific watcher.


**REST API:**

```http
GET https://workspace.oblien.com/watchers/1
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
```


**cURL:**

```bash
curl "https://workspace.oblien.com/watchers/1" \
  -H "Authorization: Bearer $GATEWAY_JWT"
```


### Response

```json
{
  "id": "1",
  "root": "/app/src",
  "dirs": 42,
  "excludes": ["node_modules", ".git"]
}
```

---

## Delete watcher

Stop a watcher and release its resources.


**SDK:**

```typescript
await rt.watcher.delete('1');
```


**REST API:**

```http
DELETE https://workspace.oblien.com/watchers/1
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
```


**cURL:**

```bash
curl -X DELETE "https://workspace.oblien.com/watchers/1" \
  -H "Authorization: Bearer $GATEWAY_JWT"
```


### Response

```json
{
  "success": true
}
```

---

## WebSocket events

Watcher events are delivered as JSON text frames on the `"watcher"` channel of the `/ws` WebSocket. All events include the `watcher_id` so you can distinguish events from multiple watchers on the same connection.

### `ready`

Sent immediately after a watcher is created and has finished scanning the directory tree.

```json
{
  "channel": "watcher",
  "type": "ready",
  "watcher_id": "1",
  "root": "/app/src",
  "dirs": 42
}
```

### `change`

Sent when a file is created, modified, deleted, or renamed within the watched directory.

```json
{
  "channel": "watcher",
  "type": "change",
  "watcher_id": "1",
  "path": "/app/src/index.ts",
  "op": "write"
}
```

#### Operations

| `op` value | Trigger |
|------------|---------|
| `create` | File or directory created, or moved into the watched tree |
| `write` | File content modified or saved |
| `remove` | File or directory deleted |
| `rename` | File or directory moved out of the watched tree |

Events are **debounced** - rapid changes to the same path within 50ms are collapsed into a single event with the last operation.

### `overflow`

Sent when the event queue overflows. This means some events may have been lost.

```json
{
  "channel": "watcher",
  "type": "overflow",
  "watcher_id": "1",
  "message": "Event queue overflow, some events may have been lost"
}
```

If you receive an overflow event, re-sync the file tree by [listing the directory](/docs/runtime-api/files#list-directory) to get the current state.

---

## Listening for events

Connect to the same WebSocket used for [terminal I/O](/docs/runtime-api/terminal#websocket). Text frames with `"channel": "watcher"` are file watcher events.

```javascript
const socket = new WebSocket('wss://workspace.oblien.com/ws', {
  headers: { Authorization: `Bearer ${gatewayJwt}` },
});

socket.onmessage = (event) => {
  if (typeof event.data === 'string') {
    const msg = JSON.parse(event.data);

    if (msg.channel === 'watcher') {
      switch (msg.type) {
        case 'ready':
          console.log(`Watcher ${msg.watcher_id} ready: ${msg.root} (${msg.dirs} dirs)`);
          break;
        case 'change':
          console.log(`${msg.op}: ${msg.path}`);
          break;
        case 'overflow':
          console.log('Events may have been lost, re-syncing...');
          break;
      }
    }
  }
};
```

---

## Limits

| Limit | Value |
|-------|-------|
| Max concurrent watchers | 5 per workspace |
| Debounce interval | 50ms per path |
| Auto-watch new subdirectories | Yes |
| Excludes | Merged with defaults, glob matching |

---

## Error responses

| Status | Meaning |
|--------|---------|
| `400` | Missing or invalid `path` |
| `401` | Missing or invalid token |
| `404` | Watcher not found |
| `409` | Already at 5 watchers limit |
| `500` | Failed to create file watcher |
