Querying from the CLI
Run KQL with bzrk search, stop early, follow new rows and read query history
bzrk search runs KQL from the terminal. Install the CLI and sign in first: Berserk CLI.
bzrk search "<KQL>" --since "<TIME>" [--until "<TIME>"]With no --since and no time bound in the query, the last hour is searched. Pass --since (and optionally --until, default now) for any other window; a window and a time filter in the query (| where timestamp > ago(6h)) both apply, and the narrower wins.
bzrk search "default | take 10" --since "1h ago"
bzrk search "default | count" --since "24h ago" --no-streamStreaming snapshots
bzrk search on a TTY (and in --agent mode) prints successive snapshots of the same --since/--until window as more of it is scanned. The requested window does not change.
An early table is not the full-window answer. A running count() or sum() is a lower bound. min(), max(), and avg() on an early snapshot are real numbers for the slices scanned so far, not globals.
| How you run it | What you get |
|---|---|
| TTY | Live table that updates as coverage grows |
--agent (or auto-detected in Claude Code / Codex) | Increment headers plus TSV under ~/.cache/bzrk/history/<id>/; each header line ends with the absolute path of its TSV snapshot |
| Pipe / redirect, not an agent | Waits for the last snapshot |
--json / --csv / --tsv / --no-stream | Last snapshot only |
--no-stream is the flag for scripts and for any caller that must not see a provisional table.
Stopping early with --stop-when
For threshold questions ("are there at least 50 errors?"), let the CLI stop itself the moment a completed snapshot decides the answer:
bzrk search "default | where severity_text == 'ERROR' | summarize n=count()" \
--since "7d ago" --stop-when "n >= 50"Existence (rows >= 1 or rows > 0) on a row-producing query that is not already limited and not an aggregate also appends | take 1, so the engine stops scheduling more work. The result is a single row — enough to answer "does a match exist?" If you want to see the matching rows, run without --stop-when or use a column threshold. Already-limited last operators (take, limit, top) and aggregate last operators (count, summarize, make-series) are left alone, as are pipelines that cannot legally be extended (render, otel-log-stats, a trailing ;). Column thresholds (n >= 50) and rows >= 2 do not inject take 1 — those need the scan until the count crosses.
bzrk search "default | where severity_text == 'ERROR'" \
--since "7d ago" --stop-when "rows >= 1"The predicate is <ident> <op> <number> where <ident> is rows (snapshot row count) or a column name (first row's numeric value), and <op> is one of >=, <=, >, <, ==, !=. Because a column predicate reads only the first row, express "any row crosses X" over a multi-row summarize by sorting that column to the top — | summarize n=count() by service | sort by n desc with --stop-when "n > 1000" stops the moment any single service crosses the line. When it holds, the scan is cancelled, a # Stopped Early header names the predicate and (in agent mode) the deciding snapshot's absolute TSV path, and the run records a completed_early outcome. The result is a partial lower bound over the requested window, not the full-window value — exactly what a threshold question needs. Predicates a partial cannot decide (absence, exact min/max/count over the whole window) still need the full run.
For conditions the predicate grammar cannot express, --stop-cmd runs a shell command against each completed snapshot's TSV instead (unix only, mutually exclusive with --stop-when). The command runs via sh -c with the snapshot's absolute TSV path as $1 (also exported as BZRK_SNAPSHOT_TSV, with BZRK_INCREMENT and BZRK_ROWS), and exit codes follow grep: 0 stops the query, 1 keeps scanning, anything else aborts the run as a broken condition.
# Stop as soon as any snapshot mentions OOMKilled
bzrk search "default | where severity_text == 'ERROR' | take 500" \
--since "24h ago" --stop-cmd 'grep -q OOMKilled "$1"'
# Stop once 50 data rows (excluding the header) have arrived
bzrk search "default | where severity_text == 'ERROR' | take 500" \
--since "24h ago" --stop-cmd '[ "$(tail -n +2 "$1" | wc -l)" -ge 50 ]'
# Stop when column 2 of any row crosses 0.99
bzrk search "default | summarize p = max(ratio) by service" \
--since "6h ago" --stop-cmd 'awk -F"\t" "NR>1 && \$2>0.99 {exit 0} END {exit 1}" "$1"'Agents
Do not treat the first increment as the answer. For threshold, existence (rows >= 1), and newest-N (rows >= N) questions, pass --stop-when; for string matches or multi-column conditions, pass --stop-cmd — both run in the foreground with no monitoring loop. Only when the stop decision needs judgment (reading the data, not testing it) monitor the increment headers yourself and stop the query when satisfied; wait for the last snapshot only when a partial cannot decide the question. See Connect Your AI Agent.
Following new rows with --follow
bzrk search -f is Live tail for the terminal: it prints the query's result, then keeps printing new rows as they arrive until you press Ctrl-C.
bzrk search -f "default | where resource['service.name'] == 'checkout' and severity_number >= 17"How it finds new rows, what it prints, and its limitations: Following new rows.
Reading the table
Headers name the columns only; --types adds each column's type (--json always carries them). A result that still carries $raw, such as default | take 10, shows timestamp, the severity and the body first, the other columns after, and $raw last, where a narrow terminal trims it. The header repeats under the table only when the table is taller than the terminal.
--stats adds the execution statistics; --stats=full also lists the engine's raw counters, with times in seconds and sizes in MB.
render timechart saves PrimaryResult.svg in the search's history directory, next to its TSV, and prints the path; --output-dir <DIR> saves it there instead. The CLI draws no other chart kind: render barchart and the rest print the table and a warning on stderr.
Query history
Every bzrk search is recorded under ~/.cache/bzrk/history/ (or $XDG_CACHE_HOME/bzrk/history/), whatever its output mode and however it ended: completed, incomplete, stopped early, cancelled with Ctrl-C, rejected by the server, or rejected locally as a parse error. A --follow run is recorded once, when it ends: stopped by you (Ctrl-C or a closed pipe) or failed. Each run gets a <trace id>/ directory with query.kql, the result TSVs, and analysis.json (outcome, stats, warnings) or error.json (code, message, category).
bzrk history # the 20 most recent searches
bzrk history --failures -n 50 # only failed ones
bzrk --json history # the same list as a JSON array
bzrk history show 287f5b925d2069 # analysis.json / error.json; a unique trace id prefix is enough
bzrk history attribute outcome.kind