Tool

watch

Run a program and turn what it does into tables you can query. watch collects the program’s traces, logs and metrics, and samples its CPU and memory, so you can ask SQL questions about a run instead of reading its output.

If youUse
Want to profile a program, or find why it is slow, failing or hangingwatch
Want to query a run’s telemetry once it is capturedquery over the watch schema
Want to find the code behind a slow operationexplore
Want a step-by-step profiling workflowProfiling with watch

What gets captured

watch starts the program with its OpenTelemetry settings pointed at the RepoQL host. Anything the program exports arrives as spans, logs and metrics. RepoQL also samples the CPU and memory of the program and every process it starts.

Running under watch doesn’t add tracing to a program. Spans and logs appear only if the program has an OpenTelemetry SDK and exports them. A program without one still gets the CPU and memory samples. A very short program can exit before its first metrics are exported, so let it run for a few seconds.

The program inherits your environment. You can add variables, for example to switch on an application’s own telemetry. RepoQL’s exporter settings, such as endpoints and protocols, always win, so capture can’t be turned off by accident. Your own service name is kept.

Keep to one run

Every run shares the watch schema. Each has its own run ID. Pass that ID to the query macros, such as watch.summary('<run_id>'), so another run’s data doesn’t mix into your measurement.

Through MCP, watch returns as soon as the program has started, so you can query while it runs. There is no MCP call to stop it: stop the program itself. In the terminal, rql watch stays attached, prints the program’s output and returns when it exits.

Example: watch a short script

Terminal

rql watch -- python3 -c "import time; t=time.time(); n=0
while time.time()-t<6: n+=1
print(n)"

Output

[rql watch] run: 233ae9daecd04ba2978685f439f0595f
[rql watch] pid: 69245
[rql watch] schema: watch
[rql watch] endpoint: http://127.0.0.1:59279/
[rql watch] telemetry_schema: watch
Wait for this app to exit: rql monitor --target 233ae9daecd04ba2978685f439f0595f --until exited --timeout 1h
  Exit 0 means the app exited successfully; failure exits 1. Works after the app has already exited.
Observe its output: rql monitor --target 233ae9daecd04ba2978685f439f0595f --since 5m --jsonl
[rql watch] query telemetry:
  SELECT * FROM watch.summary('233ae9daecd04ba2978685f439f0595f');  -- payloads arriving? exited? slowest spans
  SELECT * FROM watch.errors('233ae9daecd04ba2978685f439f0595f');   -- exceptions with innermost frames
  SELECT severity_text, body_text FROM watch.otel_log WHERE run_id = '233ae9daecd04ba2978685f439f0595f' ORDER BY observed_time_unix_nano DESC LIMIT 25;
  SELECT * FROM watch.surface;                          -- everything queryable
39342610

Terminal

rql query "SELECT * FROM watch.summary('233ae9daecd04ba2978685f439f0595f')"

Output

run_id	executable	started_at	status	duration	capture	spans	logs	metrics	errors	hints
233ae9daecd04ba2978685f439f0595f	python3	2026-09-29 06:38:15.331327	exited 0 · last event 5s ago	7s	7 payloads (metrics=7), 8061 bytes, 06:38:16-06:38:22	none	none	8 metrics, mem peak 8683520.0B, cpu total 4.701s	none	available_metrics(), surface

Terminal

rql query "SELECT name, type, unit, point_count, query_hint FROM watch.available_metrics() ORDER BY name"

Output

name	type	unit	point_count	query_hint
process.cpu.time	sum	s	12	SELECT * FROM watch.otel_metric_point WHERE metric_name = 'process.cpu.time'
process.memory.usage	gauge	By	6	SELECT * FROM watch.otel_metric_point WHERE metric_name = 'process.memory.usage'
process.memory.virtual	gauge	By	6	SELECT * FROM watch.otel_metric_point WHERE metric_name = 'process.memory.virtual'
process.thread.count	gauge	{thread}	6	SELECT * FROM watch.otel_metric_point WHERE metric_name = 'process.thread.count'
repoql.watch.process.cpu.time	sum	s	6	SELECT * FROM watch.otel_metric_point WHERE metric_name = 'repoql.watch.process.cpu.time'
repoql.watch.process.tree.memory.usage	gauge	By	7	SELECT * FROM watch.otel_metric_point WHERE metric_name = 'repoql.watch.process.tree.memory.usage'
repoql.watch.process.tree.memory.virtual	gauge	By	7	SELECT * FROM watch.otel_metric_point WHERE metric_name = 'repoql.watch.process.tree.memory.virtual'
repoql.watch.process.tree.process.count	gauge	{process}	7	SELECT * FROM watch.otel_metric_point WHERE metric_name = 'repoql.watch.process.tree.process.count'

Empty isn’t failure. No spans can mean the program has no tracing, sends its telemetry somewhere else, or hasn’t exported a batch yet. Check the capture column, or the raw watch.payload table, before you conclude anything.

Where to go next in the data

SELECT * FROM watch.surface lists everything you can query, with an example call for each. The ones you’ll use most:

QueryAnswers
watch.summary()Each run’s status, capture counts, signals and next queries.
watch.errors()Failures grouped by fingerprint, with first and last seen times.
watch.span_stats()Operations ranked by volume, errors, latency and total time.
watch.trace_tree('<trace_id>')One trace as a tree of spans with timings.
watch.available_metrics()Metric names, units and a query for each.
watch.watch_runEvery run: its process, arguments, exit code, and the OpenTelemetry settings it received.

Telemetry is stored on your machine, under the host repository’s .repoql/cache/otel/. Runs that finished more than six hours ago can be cleaned up, so keep any numbers you need. This is separate from RepoQL’s own usage telemetry.

Parameters

MCP · CLIMeaning
executable · first argumentRequired. The program to run: a name on your PATH or a file path.
arguments · the arguments after the programArguments passed to the program exactly as given, without a shell. In the terminal, put -- before the program so its flags aren’t read as rql’s.
workingDirectory · --cwdThe program’s working directory. Defaults to the directory you run from.
environment · --envExtra variables as key=value;key2=value2. %VAR% expands; escape a literal ;, = or \ with a backslash.
CLI only: --listenKept for compatibility. Only its default is accepted.
CLI only: rql watch envPrints the OpenTelemetry variables for a program you start yourself, such as one run by an orchestrator or Docker. It starts nothing.

Further reference

For an Aspire app, Observe, fix and optimise with Aspire sends every service’s telemetry to the same watch tables.

In the installed manual: help:///tools/watch/watch.md, help:///tools/watch/watching-claude.md, help:///commands/watch.md and help:///patterns/profiling.md.