Docs Query language
View as Markdown

Query language

Every --where key, operator and facet that sessions, usage, stats and the viewer's filter bar accept, with examples.

--where takes space-separated terms. Every term must match, and a leading - negates one. The same query works in coding.sh sessions, search, usage, stats, export, the viewer's filter bar, the viewer API and the MCP tools. For SQL over the index, see coding.sh sql.

Filter with --where

shell
coding.sh sessions --where 'agent:codex cost>0.5 since:7d'
coding.sh sessions --where 'tool:Bash ok:false -flag:compaction'
coding.sh sessions --where '(model:claude-* OR provider:openai) has:tests sort:-cost limit:5'
coding.sh search migration --where 'agent:codex kind:tool.end'
coding.sh usage --by model --where 'repo:coding-sh tool:Bash'
coding.sh export events --where 'agent:codex tool.errors>0' > events.jsonl

Operators

FormMeansExample
key:valueThe key has this value. Text matches ignore ASCII case.agent:codex
a spaceBoth terms match.agent:codex tool:Bash
OREither side matches. Upper case only.agent:codex OR agent:gemini
( )Groups terms.(model:claude-* OR provider:openai) has:tests
-Negates a term or a group.-flag:compaction, -(agent:codex tool:Bash)
a,bEither value.agent:codex,gemini
*A wildcard in a text value, quoted or not. Inside quotes, \* is a literal star and \\ a backslash.model:claude-*, title:"fix \*"
> >= < <=Compares a number. : means equal.turns>=20
a..bA range, both ends included.turns:3..5
k M BScale a count.tokens>1M
30s 10m 2hA duration needs a unit.duration>10m
$A cost may start with $.cost>$0.5
"…"A value with spaces.title:"fix the build"
a bare wordSearches prompts, replies and tool calls. Every word must match within one event. A bare word needs a letter or a digit; for symbols only, use coding.sh search (SQLite store only).timeout

sort: and limit: set the order and the row count, and go at the top level only. A query is at most 2,000 characters.

Session keys test the session. Event keys test its events: a session matches when one of its events matches, and event keys in one group must match the same event. So tool:Bash ok:false is a failed Bash call, and -tool:Bash means no Bash call at all. In coding.sh search, event keys test each hit.

Session keys

KeyLevelMatchesExample
agentsessionAgent id, or the service name of an SDK or OpenTelemetry appagent:codex
modelsessionMain modelmodel:claude-sonnet-*
providersessionModel providerprovider:anthropic
reposessionRepo root path, or its name (else the working directory's name)repo:coding-sh
launchsessionheadless (claude -p, an SDK, codex exec) or interactivelaunch:headless
branchsessionGit branchbranch:main
dirsessionWorking directorydir:/work/*
titlesessionTitle contains (case-insensitive)title:deploy
idsessionSession idid:01H*
runsessionEval run idrun:run_*
parentsessionParent session id, for subagent sessionsparent:abc123
flagsessionAny flag or tagflag:no-verification
skillsessionSkill tag (skill:<name>)skill:pdf
hooksessionWhose hooks added text to the context: plugin:<name>, plugin, settings or unknownhook:plugin:terse
hook.tokenssessionTokens hooks added to the context (characters / 4)hook.tokens>500
sincesessionStarted at or after: 7d, 12h, today, yesterday or a datesince:7d
untilsessionStarted before (exclusive): 7d, 12h, today or a dateuntil:2026-11-01
durationsessionWall time of an ended sessionduration>10m
turnssessionUser turnsturns>=20
toolssessionTool callstools>100
tool.errorssessionFailed tool callstool.errors>0
llm.errorssessionFailed model callsllm.errors>0
filessessionFiles changedfiles>=1
eventssessionEvents capturedevents<10
tests.passedsessionPassing tests seentests.passed>0
tests.failedsessionFailing tests seentests.failed>0

Cost and token keys

KeyLevelMatchesExample
tokenssessionAll tokens: input, output, cache read and cache writetokens>1M
tokens.insessionInput tokenstokens.in>100k
tokens.outsessionOutput tokenstokens.out>10k
costsessionCost in USD, reported or estimated (where it comes from)cost>0.5
cache.breakssessionModel calls whose cached prompt prefix was gone, so the prompt was billed againcache.breaks>0
cache.lostsessionPrompt tokens billed again after a cache breakcache.lost>10k
cache.costsessionExtra USD the cache breaks cost over cache-read pricescache.cost>0.1

Tool and event keys

KeyLevelMatchesExample
unapprovedsessionDestructive or paid commands the agent ran without the user approving themunapproved>0
kindeventEvent kind (tool.end, llm.call, user.message, …; see Trace format)kind:tool.end
tooleventTool nametool:Bash
okeventThe tool call or model call succeededok:false
mcpeventMCP server of a tool callmcp:github
erroreventError text of a failed call, or an error event's message, contains (case-insensitive)error:ENOENT
event.durationeventEvent durationevent.duration>30s

has, is, sort and limit

KeyLevelMatchesExample
hassessionHas tests (test results seen), cost, repo (a git repo), error (a failed tool or model call) or titlehas:tests
issessionIs root (top level), child (a subagent), flagged (a flag with no colon), run (from an eval run) or endedis:root
sortqueryOrder by started or any number key above; - for descendingsort:-cost
limitqueryMaximum rowslimit:50

sessions and export leave out subagent sessions unless you pass --subagents. A query with id:, parent:, is:root or is:child decides that itself, so --where is:child lists subagents.

Dates and times

A date without a zone is local time: since:2026-11-09 starts at local midnight. A date with Z or an offset (2026-11-09T00:00:00Z) is that instant. today and yesterday are local midnights, and until leaves its own day out. --since and --until on every command read values the same way.

When you give a date or a day word, sessions, usage and stats print the window they used on stderr, such as Window: 2026-11-09 00:00 (UTC-06:00) to now, local time, and put it in --json as window.

Errors

A bad query exits 2 and points at the character:

output
$ coding.sh sessions --where 'agent:codex modle:x'
✗ --where: unknown key "modle" at character 13
  Query           agent:codex modle:x
                              ^
    did you mean model:?
ReasonMeans
unknown_keyNo such key. To search for the word itself, quote it.
bad_valueThe key does not take this value. The message gives one it takes.
unterminated_quote, unbalanced_parensA quote or parenthesis is not closed.
dangling_or, dangling_not, empty_groupOR, - or ( ) has nothing to work on.
bad_directivesort: or limit: is inside a group, an OR or a -.
too_longThe query is over 2,000 characters.

With --json the error carries reason, next and data.offset (0-based). The viewer API answers 400 with reason, offset and next, and the viewer shows the error under its filter bar.

Queries on Postgres, ClickHouse and other stores

On the SQLite index a query becomes one SQL statement, with every value bound as a parameter. kind, tool, ok, mcp, event.duration and error read indexes. Bare words read every candidate event, so pair them with an indexed key (kind:tool.end timeout) on a large store.

The Postgres and ClickHouse store plugins run every key, has:, is:, bare words, sort:, limit: and --facets in their own database. A plugin older than coding.sh fails on a key it does not know, with reason unsupported_store. Event rows (GET /api/events) need the SQLite store.

Any other store plugin answers session keys by reading at most the newest 20,000 sessions; event keys and bare words fail there with unsupported_store.

Flags and derails

coding.sh checks each session for failure patterns at every ingest and keeps them as flags. coding.sh sessions --flag <name> or --where flag:<name> lists the sessions that have one.

shell
coding.sh sessions --flag no-verification
coding.sh sessions --where 'is:flagged since:7d'
FlagSet when the session
premature-completionStopped while the last test run was failing, and the agent's last words did not say so.
repeated-tool-failureHad the same tool fail three times in a row with similar input. A search that found nothing is not a failure.
no-verificationChanged files, but no test, type check, lint or build ran.
compactionHad its context compacted.
test-tamperEdited a test or test config to hide failures: a skip marker, a deleted test file, or an assertion change that alone turned a run green.
duplicate-fetchRepeated three or more read-only calls in one context with the same result and no edit in between.
skill-copy-conflictUsed two copies of one skill, such as the project's and a personal one.
refusalHad a model call stop on a refusal (refusal, content_filter or safety).
text-tool-callHad the model write a tool call out as text, so it never ran.
unapproved-actionRan a destructive or paid command that the user's turn did not approve, such as git reset --hard or git push --force.
step-retried, resumedIs a durable workflow run that retried a step, or continued after a crash. Notes, not failures.

For unapproved-action, a turn approves a command when it asks for it, says yes to the agent, or gives leave in advance. A Claude Code permission prompt counts too. Dry runs, read-only commands and a package manager's own -y are never flagged.

Derails

A derail is a point where a long run went off the rails. Each one records the event and time it started, and shows in show and on the viewer's timeline. A session gets at most 25.

DerailSet whenThreshold
loopingThe same tool call returns the same result again and again, with no edit in between.5 times within 10 calls
failure-streakTool calls fail one after another. Interrupted calls and exits that are an answer (a search that found nothing) don't count.5 in a row
churnAn edit to a file is undone, then undone again.2 reverts within 60 min
no-progressA long stretch of calls with no edit, no file-writing command, no subagent and no better test result, while the context grows.80 calls, 40% repeats, context up 1.5x
stalledA tool call sat with nothing else happening, then failed or never finished.15 min
context-thrashAutomatic compactions come back to back. Manual ones don't count.Under 3 min or 15 tool calls apart

Never flagged: polling a background job (sleep, wait, BashOutput), driving a browser or desktop through MCP, waiting on a subagent or on the user, and long tool calls that succeed.

Tags

A flag with a colon is a tag, not a failure. is:flagged and the failure rate in stats leave tags out.

TagAdded when the session
skill:<name>Used a skill, through the Skill tool or a file in the skill's folder.
hook:<source>Got text in its context from another hook, such as hook:plugin:terse.
gateway:<id>Sent model calls through an LLM gateway, such as gateway:vercel.
secret:exposedHad a vendor token replaced by redaction. The agent and its provider saw it, so rotate it.
model:switchChanged model mid-session.
model:fallbackRecorded a fallback to another model, such as Claude Code's --fallback-model.
cache:lowWrote a prompt cache of at least 4,096 tokens over three or more turns, and read back less than it wrote.
fold:errorCould not be folded into stats. coding.sh ingest tries again.

Facets

--facets counts sessions per value of each key instead of listing them.

shell
coding.sh sessions --facets agent,flag
output
AGENT             SESSIONS
claude-code       28
codex             14
coding-sh-triage  8
opencode          5

FLAG             SESSIONS
no-verification  6
OptionDefault
Keysagent, model, provider, repo, launch, branch, flag, skill, hook, kind, tool, mcp
With no keysagent, model, repo, flag, skill, tool
--limit10 values per key

Each key is counted over the query without that key's own top-level terms, so --facets agent --where agent:codex still shows every agent. A count is a number of sessions, not events. The MCP tool sessions_list takes facets too.

coding.sh sql

One read-only SQL query against the SQLite index.

shell
coding.sh sql "select agent, count(*) as n from sessions group by agent"
coding.sh sql "select tool, count(*) from events where kind = 'tool.end' and ok = 0 group by tool order by 2 desc" --csv
coding.sh sql "select id, json_extract(stats, '$.usage.costUsd') as cost from sessions order by cost desc limit 10" --json
FlagDefaultWhat it does
--csvPrint CSV instead of a table.
--jsonOne document: {columns, rows, rowCount, truncated}.
--limit1000Rows returned. A note on stderr says when rows were cut.
--timeout10Seconds before the query is stopped.

Only one statement that starts with SELECT or WITH is accepted; anything that writes exits 2. The index is opened read-only, with PRAGMA query_only on. sql reads only the SQLite index: with a Postgres or ClickHouse store, use coding.sh export or the database's own client.

coding.sh export

Streams sessions, events or labels as JSONL (the default) or CSV.

shell
coding.sh export sessions --since 30d --format csv > sessions.csv
coding.sh export events --agent codex --since 7d > events.jsonl
coding.sh export labels --format csv

The filters --agent, --repo, --since, --flag, --project, --where, --subagents and --limit (the N newest sessions) pick sessions; events and labels export the rows of those sessions. With no filter, export labels exports every label, including labels on eval cases.

JSONL rows are the stored objects (Session, AgentEvent, Label in the trace format). CSV has these columns. A text cell that starts with =, +, - or @ gets a leading ', so a spreadsheet shows it instead of running it.

ExportCSV columns
sessionsid, agent, model, title, started_at, ended_at, duration_ms, repo_root, branch, parent_session_id, project, events, turns, tool_calls, tool_errors, files_changed, input_tokens, output_tokens, cost_usd, flags (space-separated)
eventsid, session_id, ts, kind, agent, model, tool, ok, duration_ms, turn_id, subagent_id, input_tokens, output_tokens, cost_usd, payload (JSON)
labelsid, session_id, case_id, criterion, value, note, by, ts

Evidence folders

export --evidence <dir> writes each matching session's raw trace to a folder, for a person or a model to read as files:

shell
coding.sh export --evidence traces/ --where 'flag:repeated-tool-failure' --limit 20

<dir>/index.md lists the sessions. Each session gets a folder with session.json, events.jsonl, one tools/NNNN-<tool>.txt per tool call with its full output, and an index.md with stats, flags and a timeline. Everything is redacted again first. A tool file is cut at 200 KB and events.jsonl at 5 MB. A folder that is not empty stops it with target_exists, unless you pass --force.

Tables

The SQLite index keeps these tables. Times are milliseconds since the epoch. Columns that hold JSON are text: read them with json_extract(col, '$.path') or json_each(col).

sessions

ColumnMeaning
idSession id
agentAgent id: claude-code, codex, …
agent_versionAgent version, when known
modelMain model
cwdWorking directory
repoRepo JSON: root, branch, commit, remote
repo_rootThe repo root, for filtering
titleFirst prompt or the agent's title
started_atStart time
ended_atEnd time (NULL while running or unknown)
statsStats JSON: events, turns, toolCalls, toolErrors, usage, latency, …
flagsJSON array of flags and tags (looping, skill:pdf, …)
run_idEval run id, for sessions an eval started
parent_session_idParent session, for a subagent session
project_idServe project (default when none)

events

ColumnMeaning
idEvent id
session_idSession it belongs to
turn_idTurn id
tsTime
duration_msDuration, for events that have one
subagent_idSubagent that produced it
agentAgent id
modelModel, for model calls
kinduser.message, assistant.message, tool.start, tool.end, llm.call, …
toolTool name, for tool events
ok1 or 0 when the event says whether it succeeded
mcpMCP server, for a tool call to an MCP tool
payloadPayload JSON (text, command, input, output, …)
blobsJSON array of blob hashes the payload points at
usageUsage JSON: inputTokens, outputTokens, cacheReadTokens, costUsd
project_idServe project

scores

Typed scores on sessions, events, dataset cases and eval runs, from people, judges, code or the API (coding.sh score, POST /api/scores).

ColumnMeaning
idScore id
targetsession, event, case or run
target_idId of what was scored
session_idSession the target belongs to, when known
nameWhat was scored, such as helpfulness
data_typenumeric, boolean, categorical or text
valueThe number, or 1 and 0 for a boolean (NULL otherwise)
string_valueThe value of a categorical or text score
sourcehuman, judge, code, api or monitor
config_idScore config the value was checked against
judge_versionJudge version, for a judge's score
calibrated1 when the judge passed calibration
commentFree-text comment
byWho scored it
tsTime

score_configs

ColumnMeaning
idConfig id
nameScore name it applies to
data_typenumeric, boolean, categorical or text
docThe config as JSON: min, max, categories, description

labels

A view over scores: a person's boolean scores (pass or fail) and categorical pass, fail or skip scores on sessions and dataset cases.

ColumnMeaning
idLabel id
session_idLabelled session
criterionWhat was labelled, such as outcome
valuepass, fail or skip
noteFree-text note
byWho labelled it
tsTime
case_idDataset case, for a label on an eval case

runs

ColumnMeaning
idRun id
experiment_idExperiment it belongs to
task_idEval task
bundle_idHarness bundle
session_idCaptured session of the run
pass1 when it passed
started_atStart time
docThe full run as JSON

session_facets holds each session's distinct kind, tool and mcp values. Other tables (tasks, bundles, datasets, experiments, judges, kv, cursors) hold eval and bookkeeping state as JSON in a doc column. events_fts is the full-text index behind coding.sh search. List every table with:

shell
coding.sh sql "select name, sql from sqlite_schema where type = 'table'"