octolens feeds watch is the live view: it polls your mention stream and
renders new mentions as they arrive. On a terminal it is a scrolling stream
with keyboard triage; piped or under --json it degrades to a plain,
tailable line-per-mention feed an agent can act on. The full stream contract
— every flag, the exact NDJSON record shapes, poll error classification —
lives on the Feeds reference.
Start watching
Watch everything, one saved feed, or an ad-hoc filter — the same filter flagsmentions list takes:
octolens feeds watch
Flags
Triage from the keyboard (interactive TTY)
On a terminal the watch renders a scrolling stream above a focus panel showing the selected mention in full, with a status line as the liveness indicator:
The triage keys perform the exact same update as
mentions update
(Triage your mentions) — a mark here is a real
relevance/sentiment override, not a local annotation.
The backlog: what the stream starts with
By default the first poll emits the feed’s current first page — the ~20 newest already-existing mentions, possibly hours old — as if they had just arrived; pass--backlog none when you do not want that. The default burst
is right when a human opens the view (context above the live edge) and wrong
when a script acts on every line. --backlog none makes the stream
tail-only: the first page only seeds the watermark, and nothing that
existed at startup is ever emitted.
--backlog none whenever an agent acts on the stream. It is the one
guard an auto-triage or alerting loop needs: with no initial burst there is
nothing pre-existing to filter out, and after it the stream only moves
forward in time — the watch keeps a high-water mark on the newest timestamp
it has emitted and never emits anything older, so triaging mentions out of
your own filter cannot backfill old mentions into the stream.
The NDJSON stream contract (--json)
Under --json, stdout is NDJSON: one canonical Mention JSON document per
line (the exact mentions get shape), flushed as each new mention arrives.
Two properties make it safe to build on:
The stream says how it ended. If a fatal poll error (expired key, missing
scope, deleted feed) kills the watch after it has emitted mentions, the
last line is a terminator record, not a mention:
type field, so .type is an unambiguous
discriminator. emitted is how many mentions the stream handed you;
watermark is the newest timestamp it reached — where to resume from. The
same typed error also goes to stderr as the standard
error envelope with the mapped non-zero exit
code; the terminator exists because a consumer wired only to stdout has no
ordering between the two pipes and has already acted line by line long before
the exit code exists.
Stopping is not failing. A cleanly stopped watch — q, Ctrl-C, SIGINT,
SIGTERM (so timeout 60 octolens feeds watch --json too) — exits 0 with
no terminator record: everything already emitted stays on stdout and the
stream simply ends. That is how you tell “cancelled” from “died”: no
terminator and exit 0 means you stopped it; a terminator (or a non-zero
exit) means the stream failed and tells you why and how far it got. Transient
failures (network blips, rate limits) produce neither — the watch retries
with backoff and the stream is not over; its diagnostics go to stderr, one
JSON object per line.
The jq buffering trap
The CLI flushes every line as it is written — and your pipeline can still sit silent for minutes. The reason is downstream:jq, grep and sed
line-buffer only while their stdout is a terminal. The moment their stdout is
a pipe (the next |) or a file, they switch to ~4 KiB block buffering,
so a slow live stream gets held inside the filter until the block fills or
the stream ends — delivering everything at once, precisely when “live” no
longer matters.
So in a live pipeline, every filter stage after the watch must be told to
stay unbuffered: jq --unbuffered, grep --line-buffered, sed -u. The
recipes below do this; docs on this site are lint-checked for it, and yours
should copy the habit.
Recipes
Live-tail one keyword and print each relevant mention’s URL as it arrives:select(.type != "error") guard drops the terminator record so the
projection never yields a literal null — without it, a loop consuming ids
would end by processing the id null.
Auto-triage: mark every incoming negative Reddit mention not-relevant, live.
--backlog none keeps the loop off the pre-existing backlog (tail-only, so
there is no initial burst to guard against), and the .type guard keeps the
terminator out of the id stream:
- The terminator branch ends with
exit 1, notbreak: the loop is the pipeline’s last stage, so its status becomes the pipeline’s. Abreakwould let the pipeline exit0— a fatal stream error silently reported as a clean shutdown. set -o pipefailcovers the stream that dies before it starts. The terminator only exists once the stream has emitted at least one mention; a watch that fails on its very first poll (expired key, deleted feed) leaves stdout empty and exits non-zero with the error envelope on stderr. Withoutpipefailthe loop stage would hit EOF, return0, and mask that failure; with it, the watch’s own exit status becomes the pipeline’s.
--json) piped watch emits one tab-delimited record per mention
— greppable and tailable, ideal for a log file:
timeout,
both of which end it cleanly. Feeds worth watching are feeds worth saving:
build them in Build feeds & filters.