Watch SDK
The Watch SDK provides file watching and automatic rebuild functionality. Enable hot-reload development workflows where source file changes automatically trigger rebuilds and process restarts — no manual intervention needed.
Quick start
Watch and rebuild
Add the --watch flag to any build command:
# Watch for changes and rebuild automatically
rbs build //src:app --watch
# Output:
# 🔄 Initial build...
# ✅ Build succeeded
# 👀 Watching for changes... (Ctrl+C to stop)
# Extensions: [.py .js .ts]
# Directories: [/workspace/src]
Watch and run
Combine --watch with rbs run to automatically restart your application on changes:
# Watch, rebuild, and restart the process
rbs run //src:server --watch
# Output:
# 🔄 Initial build...
# ✅ Build succeeded
# 🚀 Starting: ./server --port 8080
# ──────────────────────────────────────────────────
# Server listening on :8080
# 👀 Watching for changes... (Ctrl+C to stop)
When you save a file, the watcher detects the change, stops the running process, rebuilds, and restarts:
🔄 Changes detected:
📝 src/handlers.py
📝 src/routes.py
⏹️ Stopping process...
🔨 Rebuilding...
✅ Build succeeded in 1.2s
🚀 Starting: ./server --port 8080
──────────────────────────────────────────────────
Server listening on :8080
👀 Watching for changes... (Ctrl+C to stop)
CLI flags
rbs build //target --watch [flags]
rbs run //target --watch [flags]
| Flag | Description | Default |
|---|
--watch | Enable watch mode. | false |
--debounce <ms> | Milliseconds to wait after last change before rebuilding. | 300 |
--clear | Clear the screen on each rebuild. | false |
--no-clear | Don’t clear the screen on rebuild. | — |
--extensions <ext> | Override watched file extensions (comma-separated). | Auto-detected |
Configuring watch behavior in rules
Rules can specify their own watch behavior using the ctx.watch API. This lets rule authors define sensible defaults for each language or framework.
ctx.watch.config()
Set watch configuration from inside a rule implementation:
def _my_server_impl(ctx):
# ... build logic ...
# Configure watch mode for this target
ctx.watch.config(
extensions = [".py", ".html", ".css"],
ignore_patterns = ["__pycache__/", "*.pyc", ".rbs/"],
extra_watch_dirs = ctx.attr.watch_dirs,
debounce_ms = 300,
clear_screen = True,
)
return DefaultInfo(executable = output)
Configuration options
| Option | Type | Default | Description |
|---|
extensions | list[string] | [] (all files) | File extensions to watch (e.g., .py, .ts). |
ignore_patterns | list[string] | [".rbs/", ".git/"] | Glob patterns to ignore. |
extra_watch_dirs | list[string] | [] | Additional directories to watch beyond the target’s sources. |
debounce_ms | int | 300 | Wait time in ms after the last change before triggering a rebuild. |
clear_screen | bool | false | Clear terminal screen before each rebuild. |
signal_reload | bool | false | Send SIGHUP instead of restarting the process (for servers that support hot reload). |
ctx.watch.on_reload()
For servers that support hot reload without a full restart, define a custom reload command:
def _dev_server_impl(ctx):
# ... build logic ...
# Support hot reload via custom endpoint
ctx.watch.on_reload(
command = "curl -X POST localhost:8080/__reload",
)
Language examples
Python
# BUILD.rbs
load("@rbs//python/rules.rbs", "py_binary")
py_binary(
name = "app",
srcs = ["main.py"],
deps = [":lib"],
)
Watch behavior:
- Extensions:
.py
- Ignores:
__pycache__/, *.pyc
- Debounce: 300ms
Node.js / TypeScript
# BUILD.rbs
load("@rbs//nodejs/rules.rbs", "nodejs_binary")
nodejs_binary(
name = "dev_server",
entry_point = "server.ts",
)
rbs run //:dev_server --watch
Watch behavior:
- Extensions:
.js, .ts, .jsx, .tsx, .css, .html
- Ignores:
node_modules/, dist/
- Debounce: 200ms
Java / Kotlin (Spring Boot)
# BUILD.rbs
load("@rbs//java/rules.rbs", "java_binary")
java_binary(
name = "server",
srcs = glob(["src/**/*.java"]),
main_class = "com.example.Application",
deps = [":spring_boot_web"],
)
rbs run //:server --watch
Watch behavior:
- Extensions:
.java, .kt, .properties, .yaml
- Ignores:
target/, build/
- Debounce: 500ms
Custom watch configuration
def _custom_dev_impl(ctx):
output = ctx.actions.run(
command = "make build",
outputs = [ctx.actions.declare_file("app")],
)
ctx.watch.config(
extensions = [".c", ".h", ".cpp", ".hpp"],
ignore_patterns = ["build/", "*.o", "*.a"],
extra_watch_dirs = ["include/", "lib/"],
debounce_ms = 500,
clear_screen = True,
)
return DefaultInfo(executable = output)
custom_binary = native.define_rule(
implementation = _custom_dev_impl,
attrs = {
"srcs": attr.label_list(),
},
)
How debouncing works
The watcher uses debouncing to batch rapid file changes into a single rebuild. When multiple files are saved in quick succession (e.g., from an IDE “Save All”), only one rebuild is triggered:
File change: main.py → Start 300ms timer
File change: utils.py → Reset timer to 300ms
File change: handlers.py → Reset timer to 300ms
... timer expires ...
→ Rebuild once with all 3 changes
Recommended debounce settings:
| Scenario | Recommended debounce |
|---|
| Fast compile languages (Python, Node.js) | 200–300ms |
| Medium compile (Java, Kotlin) | 300–500ms |
| Slow compile (C/C++, Rust) | 500–1000ms |
| IDE auto-save enabled | 500ms |
Process management
Graceful shutdown
When files change and a process is running, the watcher:
- Sends
SIGTERM to the process group.
- Waits up to 3 seconds for graceful shutdown.
- Sends
SIGKILL if the process is still running.
- Runs the rebuild.
- Starts the new process.
Make sure your application handles SIGTERM gracefully — close database connections, flush buffers, and shut down cleanly.
Signal reload (hot reload)
For servers that support hot reload via SIGHUP, enable signal mode to avoid full restarts:
ctx.watch.config(
signal_reload = True,
)
The watcher sends SIGHUP instead of killing and restarting the process. The server can reload configuration or recompile templates without downtime.
Troubleshooting
Infinite rebuild loop
Symptom: Build triggers, outputs trigger another build, repeat.
Solution: Add output directories to ignore patterns:
ctx.watch.config(
ignore_patterns = ["build/", "dist/", ".rbs/"],
)
Too many open files
Symptom: Error about file descriptor limit.
Solution: Reduce the watched directories or increase the file descriptor limit:
ctx.watch.config(
extra_watch_dirs = ["src/"], # Watch only source directories, not the entire workspace
)
Process not stopping
Symptom: Old process keeps running after rebuild.
Solution: Ensure your application handles SIGTERM and exits cleanly. If your process spawns child processes, make sure they are in the same process group.