Watch#

The watch tool arms an event source and returns immediately; gptme keeps working and delivers one system message when the event fires. Use it instead of polling with sleep N; check — for example, to wait for CI without blocking:

watch
until gh pr checks 42 --repo gptme/gptme --every 60s --timeout 30m

Sources include run, until, stream, timer and tmux, with list, cancel and wait to manage them. The tool is opt-in (disabled_by_default); enable it with -t +watch.

Watch tool — arm event sources and get woken when they fire.

Claude Code Monitor + ScheduleWakeup parity: arm a probe/timer/stream/tmux source, keep working, and receive one system message when it fires. Events are delivered through the server wake path (SessionManager) when a server session exists, else queued and drained at STEP_PRE.

Design: knowledge/design/2026-09-10-gptme-monitors-and-completion-events.md

Instructions

Use watch whenever you would otherwise poll (`sleep N; check`) or block on a slow command: arm it, keep working, and get woken by a system message when the event fires. Prefer keep-working; never poll with `sleep N; check` (the shell refuses such chains while watch is loaded).
Arming a source:
- `watch until <cmd> --every 30s`: fire once when cmd exits 0 (e.g. `gh pr checks`)
- `watch run <cmd>`: fire when the process exits, with rc + output tail
- `watch stream <cmd>`: each stdout line is an event (Monitor); lines over 64 KiB of UTF-8 are visibly truncated within that limit, discarding the remainder until newline
- `watch timer 10m [desc]`: fire once after a duration (ScheduleWakeup)
- `watch tmux <session> --pattern <re> --stable 5s`: fire on pane match/stability
- `watch list`
- `watch cancel <id>`: disarm. For `run`/`stream`, also stop the child we spawned (otherwise the worker and pipes leak). `until`/`timer`/`tmux` have no long-lived child to kill.
- `watch wait <id> [timeout]`: blocking observation-only wait; never kills.
Verb options: --timeout on run/until/stream/timer/tmux (unit required: 30s, 5m — a bare `--timeout 30` stays on the command, so `watch run pytest --timeout 30` runs pytest's flag); --every on until; --pattern/--stable on tmux. Other `--flags` belong to the watched command (so `watch run pytest --pattern smoke` keeps --pattern). Set `--timeout` on `until` so a never-met condition still wakes you. Runaway sources are auto-cancelled by storm guards. `run`/`until`/`stream` use the same confirmation and denylist as the shell tool (allowlisted probes auto-confirm; others prompt). `list`/`cancel`/`wait`/`timer`/`tmux` do not execute a user command, so they skip it.

Examples

User
Wait for CI on PR 42 without blocking
watch
until gh pr checks 42 --repo gptme/gptme --every 60s --timeout 30m
System
Watch w1 (until) fired: condition met: All checks passed
class gptme.tools.watch.Watch

An armed event source. Fired events are batched and delivered.

__init__(id: str, kind: str, description: str, created: float, deadline: float | None = None, events: ~collections.deque[str] = <factory>, event_times: ~collections.deque[float] = <factory>, cancel_times: ~collections.deque[float] = <factory>, delivered: int = 0, seen: int = 0, cancelled: bool = False, fired: bool = False, logdir: ~pathlib.Path | None = None, proc: ~subprocess.Popen | None = None, lock: ~_thread.allocate_lock = <factory>, cancel_event: ~threading.Event = <factory>, thread: ~threading.Thread | None = None, branch: str = 'main', expired: bool = False) → None
property status: str

armed | fired | expired | cancelled (read under self.lock).

class gptme.tools.watch.WatchSnapshot

Immutable, non-destructive view of a watch for UI rendering.

__init__(id: str, kind: str, description: str, status: str, created: float, deadline: float | None, delivered: int, branch: str) → None
gptme.tools.watch.format_watch_event(watch_id: str, text: str, status: str = 'fired') → str

Render the event’s captured outcome, never the watch’s later state.

gptme.tools.watch.watch_snapshots(logdir: Path, *, branch: str | None = None) → list[WatchSnapshot]

Return snapshots of the watches armed by one conversation.

Owner-scoped, unlike _visible: logdir is required, so a UI can never render another conversation’s watches or stale global entries. branch optionally narrows to watches armed on that branch. Reading does not drain the delivery queue or change any watch.

class gptme.tools.watch.WatchSnapshot#

Immutable, non-destructive view of a watch for UI rendering.

__init__(id: str, kind: str, description: str, status: str, created: float, deadline: float | None, delivered: int, branch: str) → None#