watch_process / watch_process_events
watch_process is the MCP tool for the “tail a dev server and tell me when it crashes” workflow. It complements the existing background-mode Bash + BashOutput pair — those give you raw bytes and an exit code; watch_process layers regex-based crash surfacing and structured events on top, so the agent can tell a crash from a normal log line without parsing ANSI-coloured stderr client-side.
watch_process(command, error_patterns?, idle_timeout_seconds?, description)— spawns the command in background and returns ashell_id.watch_process_events(shell_id, since_event_id?)— returns structured events since the last poll.KillShell— same machinery as background Bash; terminates a watched process byshell_id.
The shell_id returned by watch_process is also valid for BashOutput — the raw stdout and stderr are still captured into the same per-shell buffers. Use watch_process_events for the filtered view, BashOutput for the full byte stream.
Schema
Section titled “Schema”watch_process
Section titled “watch_process”| Param | Type | Required | Notes |
|---|---|---|---|
command | string | yes | Shell command passed to /bin/bash -c. Same denylist as Bash. |
description | string | yes | 5-10 word description for agent context. Recorded, not executed. |
error_patterns | string[] | no | Go-regex strings matched against each stderr line. Default: ["^Error:", "^Fatal:", "^panic:", "\buncaught\b", "UnhandledPromiseRejection"]. Cap: 32 patterns. Submit an empty list to disable pattern matching entirely. |
idle_timeout_seconds | number | no | If > 0, the process is killed when no output has been observed on stdout or stderr for this many seconds. Default 0 (disabled); clamped to 3600. |
watch_process_events
Section titled “watch_process_events”| Param | Type | Required | Notes |
|---|---|---|---|
shell_id | string | yes | ID returned by watch_process. |
since_event_id | number | no | Only return events with ID > this value. Default 0 (all events so far). |
Event model
Section titled “Event model”Each event carries:
ID— 1-based monotonic sequence. Pass the max ID you’ve seen back assince_event_idon the next poll.Type—started/error/idle_timeout/exited.At— RFC 3339 timestamp.Pattern— forerror: the regex source string that matched.Line— forerror: the full stderr line; foridle_timeout: the kill reason.ExitCode— forexited.
Rendered in the tool body as one event per line:
[1] 2026-04-24T15:59:59+01:00 started[2] 2026-04-24T15:59:59+01:00 error (matched "^panic:") panic: runtime error[3] 2026-04-24T15:59:59+01:00 exited exit=1Event cap
Section titled “Event cap”Each shell’s event log is capped at 1024 events. When the cap is reached the oldest ~10% are dropped in one go; subsequent calls to watch_process_events include a header note so the agent can tell some events have been missed:
NOTE: 102 earlier events dropped (per-shell cap of 1024 reached)A pattern-heavy process that hits the cap is almost always a crash-loop; the cap is meant to bound memory rather than be a normal working limit.
Behaviour details
Section titled “Behaviour details”- Stderr is read line-by-line via
bufio.Scanner(64 KiB initial buffer, 1 MiB max). Lines longer than the max are dropped — consistent with how backgroundBashhandles pipe overflow. - Stdout is still captured byte-for-byte into the per-shell 1 MiB buffer but not pattern-matched. Dev-server crashes typically surface on stderr; stdout is normal log.
- The idle watchdog polls once per second and kills the whole process group (same semantics as
KillShell) withSIGKILLwhen the configured timeout has elapsed with no output. A followingexitedevent is stamped by the lifecycle goroutine once the child reaps. - First match wins for any given stderr line — if two patterns overlap, only the earlier one in
error_patternsfires.
Failure modes
Section titled “Failure modes”| Condition | Result |
|---|---|
Missing command or description | Error result |
command triggers the Bash denylist | Error result (same as Bash) |
error_patterns contains an invalid regex | Error result naming the offending index + source |
| More than 32 patterns | Error result |
watch_process_events called with an unknown shell_id | Error result |
watch_process_events called on a shell started by Bash (not watch_process) | Error result telling the agent to use BashOutput instead |
Typical flow
Section titled “Typical flow”1. watch_process({command: "pnpm dev", description: "next dev server"}) → shell_id: abc-123
2. watch_process_events({shell_id: "abc-123"}) → [1] started [2] error (matched "^Error:") Error: Module not found ...
3. (agent fixes the import, hits Save — next.js reloads)
4. watch_process_events({shell_id: "abc-123", since_event_id: 2}) → events (0) # no new events; server is healthy
5. KillShell({shell_id: "abc-123"}) → killed: abc-123Not included
Section titled “Not included”event_logwatchdog for stdout — intentionally off; dev-server crashes don’t go to stdout in any framework we target.- Per-event deduplication — repeated matching lines emit repeated events by design (a crash loop emitting 20 “panic:” lines in 1 second is signal, not noise).
- Backpressure on very fast crash loops — the 1024-event cap + drop-oldest policy is the bound; there is no sliding-window rate-limit.
Related
Section titled “Related”- Bash — foreground shell / background shell without filtering.
- BashOutput — raw bytes for a background / watched shell.
- KillShell — terminate a background or watched shell.
- Agent-health metrics — tool-error-rate / time-since-last-green gauges complement watch_process for “is this agent session productive?” signals.